leetcode並查集相關經典題目(思路、分析、代碼)

leetcode並查集相關經典題目(思路、分析、代碼)

關於並查集的一些基礎知識以及應用,可以看我之前的一篇文章:一文搞定並查集
看完那篇文章基本可以完全掌握並查集

547. 朋友圈

班上有 N 名學生。其中有些人是朋友,有些則不是。他們的友誼具有是傳遞性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那麼我們可以認爲 A 也是 C 的朋友。所謂的朋友圈,是指所有朋友的集合。

給定一個 N * N 的矩陣 M,表示班級中學生之間的朋友關係。如果M [i] [j] = 1,表示已知第 i 個和 j 個學生互爲朋友關係,否則爲不知道。你必須輸出所有學生中的已知的朋友圈總數。

示例 1:
輸入: 
[[1,1,0],
 [1,1,0],
 [0,0,1]]
輸出: 2 
說明:已知學生0和學生1互爲朋友,他們在一個朋友圈。
第2個學生自己在一個朋友圈。所以返回2

分析:該題是典型的並查集應用,如果兩個人是朋友,則將其歸爲一類,採用並查集表示,首先初始化,然後遍歷,如果兩個人是朋友且未在一類則unite。最後查看有多少個集合即可。

  • 並:如果兩個節點的屬於一類,則 ,方法是將其中一個節點的根指向另一個節點的根即可
  • 查:判斷兩個節點是否是一類只需要判斷他們的根是否相同
  • 由於該題的節點數量較少,故沒有過多優化。實際上可以添加一個rank數組大致表示節點所在樹的高度,當合並時令低樹的根指向高樹的根可以降低樹的高度。
class Solution {
public:
	int par[201]; 
    void init(int n) //初始化並查集
	{
		for(int i=0;i<n;i++)  //讓其父結點指向自己 
			par[i]=i;	
	}
	int find(int x) //查找某節點所在樹的根節點
	{
		return par[x]==x?x:par[x]=find(par[x]);  //如果自己是根節點則返回,否則向上遞歸,但是將par[x]指向父親的根節點(用於優化)	
	} 
	void unite(int x,int y)
	{
		if(find(x)==find(y))//是同一類則返回 
			return;
		else
			par[find(x)]=find(y); //讓其中一個節點的根指向另一個節點的根即可 
	}
	int findCircleNum(vector<vector<int>>& M) 
	{
		int row=M.size();
		if(row==0) return 0;
		init(row); //初始化row個節點
		for(int i=0;i<row-1;i++)
		for(int j=i+1;j<row;j++)  //朋友關係是無向的,故只遍歷一半即可 
		{
			if(M[i][j]==1)
				unite(i,j);	
		} 
		int result=0;
		for(int i=0;i<row;i++)
			if(par[i]==i) //一個節點是根節點則說明有一個朋友圈 
				result++;
		return result;
		
    }
};

1319. 連通網絡的操作次數

用以太網線纜將 n 臺計算機連接成一個網絡,計算機的編號從 0 到 n-1。線纜用 connections 表示,其中 connections[i] = [a, b] 連接了計算機 a 和 b。

網絡中的任何一臺計算機都可以通過網絡直接或者間接訪問同一個網絡中其他任意一臺計算機。

給你這個計算機網絡的初始佈線 connections,你可以拔開任意兩臺直連計算機之間的線纜,並用它連接一對未直連的計算機。請你計算並返回使所有計算機都連通所需的最少操作次數。如果不可能,則返回 -1 。
在這裏插入圖片描述

分析:最初的連接中可能存在冗餘連接,即一個區域已經形成通路但仍增加了部分連接。並且,最初的連接給定後,理論上是將整個計算機分成了很多"孤島",可以視作分類。因此,在此可以採用並查集。

  • 遍歷connections
  • 如果當前給定的兩個計算機已經連通(已經在一個類),則不添加這條邊,並且記錄冗餘的連接數量+1
  • 當完成並查集建立後,計算網絡的類別數量(即孤島數量)
  • 如果冗餘連接可以將孤島全部連接起來,則說明可以完成,理論上最小操作數是 孤島數量-1.

在此由於節點非常多,因此採用了rank優化。

class Solution {
public:
	vector<int> par;
	vector<int> rank; 
    void init(int n) //初始化並查集
	{
        par.resize(n,0);
		for(int i=0;i<n;i++)
		{	
			par[i]=i; //將每個節點的根指向自身
		}
        rank.resize(n,0);
	}
	int find(int x) //查找某節點所在樹的根節點
	{
		return par[x]==x?x:par[x]=find(par[x]);  //如果自己是根節點則返回,否則向上遞歸,但是將par[x]指向父親的根節點(用於優化)	
	} 
	void unite(int x,int y)
	{
		int x_par=find(x);
		int y_par=find(y);
		if(x_par==y_par) 
			return; //是同一類,故返回
		//需要合併的話,儘量讓rank低的合併到rank高的 
		if(rank[x]<rank[y])
			par[x_par]=y_par; // 
		else
        {
            if(rank[x]==rank[y])  
                rank[x]++; //如果兩個樹高度一樣,則合併後樹的高度加1
			par[y_par]=x_par;
        }
    }
	 int makeConnected(int n, vector<vector<int>>& connections) 
	 {
		init(n); //初始化n個節點
		int length=connections.size();
		int more_connections=0; //冗餘連接 
		for(int i=0;i<length;i++)
		{
			if(find(connections[i][0])==find(connections[i][1])) //如果已經連通,則冗餘
				more_connections++;
			else
				unite(connections[i][0],connections[i][1]); //否則合併 
		}
		int set_nums=0;
		for(int i=0;i<n;i++) //查看孤島數量
		{
			if(par[i]==i)
				set_nums++;
		}
		return set_nums-1<=more_connections?set_nums-1:-1; 
     }	
};

990. 等式方程的可滿足性

給定一個由表示變量之間關係的字符串方程組成的數組,每個字符串方程 equations[i] 的長度爲 4,並採用兩種不同的形式之一:“a==b” 或 “a!=b”。在這裏,a 和 b 是小寫字母(不一定不同),表示單字母變量名。

只有當可以將整數分配給變量名,以便滿足所有給定的方程時才返回 true,否則返回 false。

示例 1:
輸入:["a==b","b!=a"]
輸出:false
解釋:如果我們指定,a = 1 且 b = 1,那麼可以滿足第一個方程,但無法滿足第二個方程。沒有辦法分配變量同時滿足這兩個方程。

示例 2:
輸出:["b==a","a==b"]
輸入:true
解釋:我們可以指定 a = 1 且 b = 1 以滿足滿足這兩個方程。

示例 3:
輸入:["a==b","b==c","a==c"]
輸出:true

分析:將相等的變量放置在一起,如果沒有與之矛盾的不等式語句,就說明該方程是可以滿足的。因此,採用並查集的方法,將相等的變量進行分類。

  • 遍歷等式,將相等的變量歸爲一類,如果等式兩邊的兩個變量已經是一類,則忽略即可
  • 遍歷不等式,如果不等式兩端的兩個變量是一類的,則返回false,否則說明該不等式可以成立

由於節點數量較少,故在此不考慮根據高度進行壓縮。

class Solution {
public:
    vector<int> par; //字母共26個
    void init() //初始化
    {
        par.resize(27,0);
        for(int i=0;i<26;i++)
            par[i]=i;
    }
    int find(int x) //查找根
    {
        return par[x]==x?x:par[x]=find(par[x]);
    }
    void unite(int x,int y)//進行合併
    {
        par[find(x)]=find(y);
    }
    bool equationsPossible(vector<string>& equations) 
    {
        init();
        for(int i=0;i<equations.size();i++)
        {
            if(equations[i][1]=='=') //只取等式
            {
                int x=equations[i][0]-'a';
                int y=equations[i][3]-'a';
                if(find(x)!=find(y))
                    unite(x,y);
            }
        }
        for(int i=0;i<equations.size();i++)
        {
            if(equations[i][1]=='!') //只取不等式
            {
                int x=equations[i][0]-'a';
                int y=equations[i][3]-'a';
                if(find(x)==find(y)) //說明x和y相等,但這裏x和y不等,故false
                    return false;
            }
        }
        return true;
    }
};

128. 最長連續序列

給定一個未排序的整數數組,找出最長連續序列的長度。

要求算法的時間複雜度爲 O(n)。

示例:
輸入: [100, 4, 200, 1, 3, 2]
輸出: 4
解釋: 最長連續序列是 [1, 2, 3, 4]。它的長度爲 4

分析:該題也可以採用並查集,實際上該題用並查集是比較巧妙的。

如果數字是連續的,則將其歸爲一類,則最終我們的目的就是找到最長的那個類的長度。

由於數組中數字是非連續的,因此如何設置並查集?可以採用unordered_map,首先遍歷數組將所有元素存儲到unordered_map中,然後便可以每次判斷相鄰數字是否存在於unordered_map中,如果在,則將其合併,只需要維護一端即可(每次判斷該數字+1是否在即可)。爲了更快的找到類的長度,可以建立一個映射,每次merge時可以將根對應的value+1,說明該類的元素數量+1,維護最大的數量即可。

class Solution {
public:
    unordered_map<int,int> par; //存儲節點到父結點的映射關係
    unordered_map<int,int> count;  //存儲每個節點對應的類的長度
    void init(vector<int> &nums)
    {
        for(auto i:nums)
        {
            par[i]=i; //父結點指向自己
            count[i]=1; //每個類都是1
        }
    }
    int find(int x)
    {
        return x==par[x]?x:par[x]=find(par[x]);
    }
    int unite(int x,int y) //在此讓返回合併後類的大小
    {
        int x_par=find(x);
        int y_par=find(y);
        if(x_par==y_par) //屬於一類
            return count[y_par];
        else
        {
            par[x_par]=y_par; //讓x對應的根指向y對應的根
            count[y_par]+=count[x_par]; //合併後的根的count爲兩個和
            return count[y_par];
        }
    }
    int longestConsecutive(vector<int>& nums) 
    {
        if(nums.size()==0) return 0;
        init(nums); //初始化
        int result=1;
        for(auto i:nums)
        {
            if(par.count(i+1)>0) //說明i+1在數組
                result=max(result,unite(i,i+1));
        }
        return result;

    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章