[LeetCode] 886. Possible Bipartition 可能的二分圖

 

Given a set of N people (numbered 1, 2, ..., N), we would like to split everyone into two groups of any size.html

Each person may dislike some other people, and they should not go into the same group. java

Formally, if dislikes[i] = [a, b], it means it is not allowed to put the people numbered a and b into the same group.git

Return true if and only if it is possible to split everyone into two groups in this way.github

  

Example 1:數組

Input: N = 4, dislikes = [[1,2],[1,3],[2,4]] Output: true Explanation: group1 [1,4], group2 [2,3] 

Example 2:數據結構

Input: N = 3, dislikes = [[1,2],[1,3],[2,3]] Output: false 

Example 3:函數

Input: N = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]] Output: false 

 

Note:post

  1. 1 <= N <= 2000
  2. 0 <= dislikes.length <= 10000
  3. 1 <= dislikes[i][j] <= N
  4. dislikes[i][0] < dislikes[i][1]
  5. There does not exist i != j for which dislikes[i] == dislikes[j].

 

這道題又是關於二分圖的題,第一次接觸的時候是 Is Graph Bipartite?,那道題給的是建好的鄰接鏈表(雖然是用數組實現的),可是本質上和這道題是同樣的,同一條邊上的兩點是不能在同一個集合中的,那麼這就至關於本題中的 dislike 的關係,也能夠把每一個 dislike 看做是一條邊,那麼兩端的兩我的不能在同一個集合中。看透了題目的本質後,就不難作了,跟以前的題相比,這裏惟一不一樣的就是鄰接鏈表沒有給咱們建好,須要本身去建。不論是建鄰接鏈表,仍是鄰接矩陣都行,反正是要先把圖建起來才能遍歷。那麼這裏咱們先創建一個鄰接矩陣好了,建一個大小爲 (N+1) x (N+1) 的二維數組g,其中若 g[i][j] 爲1,說明i和j互相不鳥。那麼先根據 dislikes 的狀況,把二維數組先賦上值,注意這裏 g[i][j] 和 g[j][i] 都要更新,由於是互相不鳥,而並非某一方熱臉貼冷屁股。下面就要開始遍歷了,仍是使用染色法,使用一個一維的 colors 數組,大小爲 N+1,初始化是0,因爲只有兩組,能夠用1和 -1 來區分。那麼開始遍歷圖中的結點,對於每一個遍歷到的結點,若是其還未被染色,仍是一張白紙的時候,調用遞歸函數對其用顏色1進行嘗試染色。在遞歸函數中,現將該結點染色,而後就要遍歷全部跟其合不來的人,這裏就發現鄰接矩陣的好處了吧,否則每次還得遍歷 dislikes 數組。因爲這裏是鄰接矩陣,因此只有在其值爲1的時候才處理,當找到一個跟其合不來的人,首先檢測其染色狀況,若是此時兩我的顏色相同了,說明已經在一個組裏了,這就矛盾了,直接返回 false。若是那我的仍是白紙一張,咱們嘗試用相反的顏色去染他,若是沒法成功染色,則返回 false。循環順序退出後,返回 true,參見代碼以下:

 

解法一:this

class Solution {
public:
    bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
        vector<vector<int>> g(N + 1, vector<int>(N + 1));
        for (auto dislike : dislikes) {
            g[dislike[0]][dislike[1]] = 1;
            g[dislike[1]][dislike[0]] = 1;
        }
        vector<int> colors(N + 1);
        for (int i = 1; i <= N; ++i) {
            if (colors[i] == 0 && !helper(g, i, 1, colors)) return false;
        }
        return true;
    }
    bool helper(vector<vector<int>>& g, int cur, int color, vector<int>& colors) {
        colors[cur] = color;
        for (int i = 0; i < g.size(); ++i) {
            if (g[cur][i] == 1) {
                if (colors[i] == color) return false;
                if (colors[i] == 0 && !helper(g, i, -color, colors)) return false;
            }
        }
        return true;
    }
};

 

咱們還能夠用迭代的寫法,不實用遞歸函數,可是整個思路仍是徹底同樣的。這裏創建鄰接鏈表,比鄰接矩陣能省一些空間,只把跟其相鄰的結點存入對應的數組內。仍是要創建一個一維 colors 數組,並開始遍歷結點,若某個結點已經染過色了,跳過,不然就先給其染爲1。而後藉助 queue 來進行 BFS 遍歷,現將當前結點排入隊列,而後開始循環隊列,取出隊首結點,而後遍歷其全部相鄰結點,若是兩個顏色相同,直接返回 false,不然若其爲白紙,則賦相反顏色,而且排入隊列。最終若順序完成遍歷,返回true,參見代碼以下:

 

解法二:
class Solution {
public:
    bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
        vector<vector<int>> g(N + 1);
        for (auto dislike : dislikes) {
            g[dislike[0]].push_back(dislike[1]);
            g[dislike[1]].push_back(dislike[0]);
        }
        vector<int> colors(N + 1);
        for (int i = 1; i <= N; ++i) {
            if (colors[i] != 0) continue;
            colors[i] = 1;
            queue<int> q{{i}};
            while (!q.empty()) {
                int t = q.front(); q.pop();
                for (int cur : g[t]) {
                    if (colors[cur] == colors[t]) return false;
                    if (colors[cur] == 0) {
                        colors[cur] = -colors[t];
                        q.push(cur);
                    }
                }
            }
        }
        return true;
    }
};

 

其實這道題還可使用並查集 Union Find 來作,所謂的並查集,簡單來講,就是歸類,將同一集合的元素放在一塊兒。那麼如何在能驗證兩個元素是否屬於同一個集合呢,這裏就要使用一個 root 數組(有時候是使用 HashMap),若是兩個元素是同一個組的話,那麼最終調用find函數返回的值應該是相同的,能夠理解爲老祖宗相同就是同一個組,兩個點的 root 值不一樣,也多是同一個組,由於 find 函數的運做機制是一直追根溯源到最原始的值。能夠看到,這裏博主的 find() 函數寫的是遞歸形式,一行搞定碉堡了,固然也有 while 循環式的迭代寫法。好,回過頭來繼續說這道題,這裏仍是首先建圖,這裏創建鄰接鏈表,跟上面的使用二維數組的方法不一樣,這裏使用來 HashMap,更加的節省空間。如今不須要用 colors 數組了,而是要使用並查集須要的 root 數組,給每一個點都初始化爲不一樣的值,由於在初始時將每一個點都看做一個不一樣的組。而後開始遍歷全部結點,若當前結點沒有鄰接結點,直接跳過。不然就要開始進行處理了,並查集方法的核心就兩步,合併跟查詢。咱們首先進行查詢操做,對當前結點和其第一個鄰接結點分別調用find函數,若是其返回值相同,則意味着其屬於同一個集合了,這是不合題意的,直接返回 false。不然繼續遍歷其餘的鄰接結點,對於每個新的鄰接結點,都調用 find() 函數,仍是判斷若返回值跟原結點的相同,return false。不然就要進行合併操做了,根據敵人的敵人就是朋友的原則,全部的鄰接結點之間應該屬於同一個組,由於就兩個組,我全部不爽的人都不能跟我在一個組,那麼他們全部人只能都在另外一個組,因此須要將他們都合併起來,合併的時候不論是用 root[parent] = y 仍是 root[g[i][j]] = y 都是能夠,由於無論直接跟某個結點合併,或者跟其祖宗合併,最終通過 find() 函數追蹤溯源都會返回相同的值,參見代碼以下:

 

解法三:
class Solution {
public:
    bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
        unordered_map<int, vector<int>> g;
        for (auto dislike : dislikes) {
            g[dislike[0]].push_back(dislike[1]);
            g[dislike[1]].push_back(dislike[0]);
        }
        vector<int> root(N + 1);
        for (int i = 1; i <= N; ++i) root[i] = i;
        for (int i = 1; i <= N; ++i) {
            if (!g.count(i)) continue;
            int x = find(root, i), y = find(root, g[i][0]);
            if (x == y) return false;
            for (int j = 1; j < g[i].size(); ++j) {
                int parent = find(root, g[i][j]);
                if (x == parent) return false;
                root[parent] = y;
            }
        }
        return true;
    }
    int find(vector<int>& root, int i) {
        return root[i] == i ? i : find(root, root[i]);
    }
};

 

討論:能夠看到本文中的三種解法在創建圖的時候,使用的數據結構都不一樣,解法一使用二維數組創建了鄰接矩陣,解法二使用二維數組創建了鄰接鏈表,解法三使用了 HashMap 創建了鄰接鏈表。刻意使用不一樣的方法就是爲了你們能夠對比區別一下,這三種方法都比較經常使用,在不一樣的題目中選擇最適合的方法便可。

 

Github 同步地址:url

 

相似題目:

 

參考資料:

https://leetcode.com/problems/possible-bipartition/

https://leetcode.com/problems/possible-bipartition/discuss/159085/java-graph

https://leetcode.com/problems/possible-bipartition/discuss/195303/Java-Union-Find

https://leetcode.com/problems/possible-bipartition/discuss/158957/Java-DFS-solution

 

LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索