Showing posts with label Union-Find. Show all posts
Showing posts with label Union-Find. Show all posts

Saturday, September 12, 2020

Leetcode 1559: Detect Cycles in 2D Grid

 https://leetcode.com/problems/detect-cycles-in-2d-grid/description/


Notes:

This question belongs to find the connected groups. One way is that we can use union-find, (or bfs, or dfs).

One of the features about the circle is that: each element on the circle has at least 2 neighbors with the same letter. So the in-degree is 2 or larger.

This can give us a way to solve this problem: 
1): we can label all the elements with in-degree less than 2 to be invalid. 
2): then we can just perform "find island" with the elements with in-degree >= 2 only;
3): if there is any "island" or connected group having a size larger > 3, return true;
4): o/w, return false; 

So step 1) is the key. One difficulty is that: say there is one element has an in-degree larger than 1, but if one or more of its neighbors (with the same letter) only has an in-degree of 1, then the in-degree of the element will be decreased by removing the invalid neighbors.

We can apply bfs to gradually label all the elements as invalid. The logic here is similar to topology sort: we will start with elements with in-degree of 1. Then gradually search its four neighbors:
1): if the neighbor is not the same letter, continue;
2): else if the neighbor has a positive in-degree, then update it by minus 1;

After handling all the four neighbors, we need to scan the four neighbor again to see the final in-degrees. If the in-degree is 1, we will push it to the bfs queue; o/w, continue (if < 1, this element has no effect on others; if >1, still belongs to valid group).

After finishing this step, all the remaining elements with in-degree > 2 will be the pool to forme "islands" or connected groups. If any of the island has a size larger than 3, return true; o/w, return false.

Here use the union-find algorithm for finding circles.  

See the code below:

class Solution {
struct UF {
    vector<int> ps, cts;
    int num, mx;
    UF(int n): ps(n, 0), cts(n, 1), num(n), mx(0) {
        iota(ps.begin(), ps.end(), 0);
        if(n>0) mx = 1;
    }
    int find(int x) {
        if(ps[x] == x) return x;
        return ps[x] = find(ps[x]);
    }
    void unite (int x, int y) {
        int a = find(x), b = find(y);
        if(a != b) {
            ps[b] = a;
            cts[a] += cts[b];
            mx = max(mx, cts[a]);
            --num;
        }
    }
};
public:
    bool containsCycle(vector<vector<char>>& grid) {
        if(!grid.size() || !grid.front().size()) return false;
        int m = grid.size(), n = grid.front().size();
        vector<int> dirs = {-1, 0, 1, 0, -1};
        vector<vector<int>> ids(m, vector<int>(n, 0));
        queue<pair<int, int>> q;
        for(int i=0; i<m; ++i) {
            for(int j=0; j<n; ++j) {
                for(int k=0; k+1<dirs.size(); ++k) {
                    int a = i + dirs[k], b = j + dirs[k+1];
                    if(a<0 || a>=m || b<0 || b>=n || grid[a][b] != grid[i][j]) continue;
                    ++ids[i][j];
                }
                if(ids[i][j] == 1) q.push({i, j});
            }
        }
        while(q.size()) {
            auto t = q.front();
            q.pop();
            int x = t.first, y = t.second;
            --ids[x][y];
            for(int k=0; k+1<dirs.size(); ++k) {
                int a = x + dirs[k], b = y + dirs[k+1];
                if(a<0 || a>=m || b<0 || b>=n) continue;
                if(grid[a][b] == grid[x][y] && ids[a][b] > 0) --ids[a][b];
            }
            for(int k=0; k+1<dirs.size(); ++k) {
                int a = x + dirs[k], b = y + dirs[k+1];
                if(a<0 || a>=m || b<0 || b>=n || grid[a][b] != grid[x][y] || ids[a][b] != 1) continue;
                q.push({a, b});
            }
        }
        UF uf(m*n);
        for(int i=0; i<m; ++i) {
            for(int j=0; j<n; ++j) {
                if(ids[i][j] < 2) continue; 
                int a = i-1, b = j-1;
                // upper 
                if(a>=0 && grid[a][j] == grid[i][j] && ids[a][j] > 1) uf.unite(a*n+j, i*n+j);
                // left
                if(b>=0 && grid[i][b] == grid[i][j] && ids[i][b] > 1) uf.unite(i*n+b, i*n+j);
            }
        }
        return uf.mx > 3;
    }
};

Sunday, September 6, 2020

Leetcode 827: Making A Large Island

 https://leetcode.com/problems/making-a-large-island/description/



Notes:

This question can be viewed as a followup to find the largest island.

One idea is that: if we meet with a 0, then we need to check its four adjacent directions. 

For each of the four valid neighbors, if the value is 0, we can continue; 
else  we need to know: 1): the island id; 2): the size of island.

The reason for knowing 1) is that: the 1 on the left may be on the same island for 1 on other sides.
If they do not belong to the same island, then we can add them up.

The maximum value + 1 (flip one 0 to 1) is the final result.

For information 1) and 2), we can obtained by BFS, DFS, or UF. Here I just share codes with UF.

See the code below:

class Solution {
public:
    struct UF {
        vector<int> ps, cs;
        UF(int n): ps(n, 0), cs(n, 1) {
            iota(ps.begin(), ps.end(), 0);
        }
        int find(int x) {
            return ps[x] == x ? x : ps[x] = find(ps[x]);
        }
        void unite(int x, int y) {
            int a = find(x), b = find(y);
            if(a != b) {
                ps[b] = a;
                cs[a] += cs[b];
            }
        }
    };
    int largestIsland(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid.front().size(), res = 0;
        UF uf(m*n);
        for(int i=0; i<m; ++i) {
            for(int j=0; j<n; ++j) {
                if(grid[i][j] == 1) {
                    int x = i-1, y = j-1;
                    if(x>=0 && grid[x][j] == 1) uf.unite(i*n+j, x*n+j);
                    if(y>=0 && grid[i][y] == 1) uf.unite(i*n+j, i*n+y);
                }
            }
        }
        vector<int> dirs = {-1, 0, 1, 0, -1};
        for(int i=0; i<m; ++i) {
            for(int j=0; j<n; ++j) {
                if(grid[i][j] == 0) {
                    set<int> st;
                    int count = 0;
                    for(int k=0; k+1<dirs.size(); ++k) {
                        int a = i + dirs[k], b = j + dirs[k+1];
                        if(a<0 || a>=m || b<0 || b>=n || grid[a][b] == 0) continue;
                        int r = uf.find(a*n + b);
                        if(!st.count(uf.ps[r])) {
                            count += uf.cs[r];
                            st.insert(r);
                        }
                    }
                    res = max(res, count+1);
                }
            }
        }
        return res == 0 ? m*n : res;
    }
};

Saturday, September 5, 2020

Leetcode 1579: Remove Max Number of Edges to Keep Graph Fully Traversable

 https://leetcode.com/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable/description/


Notes: 

This question can be solved using union-find. 

The basic idea is that: if there are type 3 edges, we should use it with the highest priority, since it means both players can use it. 

Then the next key point is that: for each edge, we will do a find search in the union-find frame. If the root of the two vertexes are the same, meaning that they have be connected already, then this edge can be removed. Otherwise, it means these two vertexes are not connected yet, so need to use the current edge. 

Another information needs to record is that: we need to whether both players can visited all vertexes or not. This can also be solved in union-find: if there is only one connected group left, then the player can visit all the vertexes. So we just need to count the number of groups in the process of union-find. 


See the code below:

 
class Solution {
public:
    int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
        int res = 0;
        vector<pair<int, int>> vt1, vt2, vt3;
        for(auto &a : edges) {
            if(a[0] == 1) vt1.push_back({a[1]-1, a[2]-1});
            else if (a[0] == 2) vt2.push_back({a[1]-1, a[2]-1});
            else vt3.push_back({a[1]-1, a[2]-1});
        }
        vector<int> uf1(n, 0), uf2(n, 0);
        // initialize number of groups (each node is one group initially)
        int ct1 = n, ct2 = n;
        for(int i=1; i<n; ++i) {
            uf1[i] = i;
            uf2[i] = i;
        }
        // use type 3 first
        for(auto &v : vt3) {
            int a = v.first, b = v.second;
            int x = find(a, uf1), y = find(b, uf1);
            // the edge can be revomed;
            if(x==y) ++res;
            // the numer of groups decreases by one
            else { uf1[y] = x; --ct1; uf2[y] = x; --ct2;}
        }
        for(auto &v : vt1) {
            int a = v.first, b = v.second;
            int x = find(a, uf1), y = find(b, uf1);
            if(x==y) ++res;
            else {uf1[y] = x; --ct1;}
        }
        for(auto &v : vt2) {
            int a = v.first, b = v.second;
            int x = find(a, uf2), y = find(b, uf2);
            if(x==y) ++res;
            else {uf2[y] = x; --ct2;}
        }
        // check the final number of groups
        if(ct1>1 || ct2>1) return -1;
        return res;
    }
private:
    int find(int x, vector<int>& vt) {
        if(vt[x] == x) return x;
        return vt[x] = find(vt[x], vt);
    }
};

Another version of code, which is more formal:

 
class Solution {
public:
    struct UF {
        vector<int> ps;
        int num;
        UF(int n): ps(n), num(n) {
            iota(ps.begin(), ps.end(), 0);
        }
        int find(int x) {
            if(ps[x] == x) return x;
            return ps[x] = find(ps[x]);
        }
        int unite(int x, int y) {
            int a = find(x), b = find(y);
            if(a == b) return 1;
            ps[b] = a;
            --num;
            return 0;
        }
        int getNum() {return num;}
    };
    int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
        int res = 0;
        vector<pair<int, int>> vt1, vt2, vt3;
        for(auto &a : edges) {
            if(a[0] == 1) vt1.push_back({a[1]-1, a[2]-1});
            else if (a[0] == 2) vt2.push_back({a[1]-1, a[2]-1});
            else vt3.push_back({a[1]-1, a[2]-1});
        }
        UF uf1(n), uf2(n);
        for(auto &v : vt3) {
            res += uf1.unite(v.first, v.second);
            uf2.unite(v.first, v.second);
        }
        for(auto &v : vt1) {
            res += uf1.unite(v.first, v.second);
        }
        for(auto &v : vt2) {
            res += uf2.unite(v.first, v.second);
        }
        if(uf1.getNum()>1 || uf2.getNum()>1) return -1;
        return res;
    }
};

Wednesday, December 25, 2019

Leetcode 947: Most Stones Removed with Same Row or Column

https://leetcode.com/problems/most-stones-removed-with-same-row-or-column/description/


Notes:

This question is equivalent to find the number of group of the "connected" points.

For each group, we can take all the stones except the last one. So the maximum number of stones that can be picked up is Total - numGroup.

Here, "connected" means the points have at least the same x or (or and) y. This can be solved by dfs or bfs after building a graph which contains the connecting information.

A union-find algorithm can also be used to this question.

For this question, we need to distinguish x and y. So we may use two unordered_maps (or other tricks, for example, adding y by 10000).

There are three cases:

1): if both x and y are not found before, it means it is a new group;

2): if one of the x and y (x or y, not both) has been labeled, it means this points must connect to a previous group;

3): if both x and y are found, there are two sub-cases:
       a): x and y belongs to the same group (same root or parent), it is the same as the above case 2);
       b): if not the same group, then this point connects two previous groups together. So we need to reduce the group number by one.

The terminal condition for the find() in the Union-find algorithm is that p(x) == x, so we can set the root or parent up in the beginning.

 See the code below:

class Solution {
public:
    int removeStones(vector<vector<int>>& stones) {
        int n = stones.size(), numGp = n;
        unordered_map<int, int> px, py;
        for(int i=1; i<=n; ++i) {//initilize the roots or parents
            px[-i] = -i;
            py[-i] = -i;
        }
        for(int i=1; i<=n; ++i) {//find number of groups
            int x = stones[i-1][0], y = stones[i-1][1];
            if(!px.count(x) && !py.count(y)) {
                px[x] = -i;
                py[y] = -i;
            }
            else {
                --numGp; //the new stone must connecte to at least one existing group, or two groups.
                if(!px.count(x)) {
                    int ry = find(y, py);
                    px[x] = ry;
                }
                else if(!py.count(y)) {
                    int rx = find(x, px);
                    py[y] = rx;
                }
                else {
                    int rx = find(x, px), ry = find(y, py);
                    if(rx != ry) {//connect two groups
                        --numGp;
                        px[ry] = rx;
                        py[ry] = rx;
                    }
                }
            }
        }
        return n - numGp;
    }
private:
    int find(int x, unordered_map<int, int>& mp) {
        if(mp[x] != x) mp[x] = find(mp[x], mp);
        return mp[x];
    }
};

Another implementation which is more general, or like a template.

See the code below:

class Solution {
public:
    int removeStones(vector<vector<int>>& stones) {
        int n = stones.size();
        numGroup = 0;//initialization
        for(auto &a : stones) {
            unite(a[0], -a[1]-1);//need to distinguish x and y
        }
        return n - numGroup;
    }

private:
    unordered_map<int, int> mp;
    int numGroup;

    int find(int x) {
        if(!mp.count(x)) {
            mp[x] = x;
            ++numGroup;
        }
        if(mp[x] != x) mp[x] = find(mp[x]);
        return mp[x];
    }

    void unite(int x, int y) {
        int rx = find(x), ry = find(y);
        if(rx != ry) {
            mp[rx] = ry;
            --numGroup;
        }
    }
};

Saturday, November 16, 2019

Leetcode 1258: Synonymous Sentences

https://leetcode.com/problems/synonymous-sentences/description/


Notes:

This is not a straightforward question.

If we know the equivalent words for each word in the text, then this question becomes straightforward: a typical combination problem which can be solved by dfs.

So the key is how to find the equivalent words?

For any two synonyms[i] and synonyms[j], if they have at least one the same string, all the strings become equivalent, or belong to the same group. So we can apply union-find like algorithm to solve grouping problem.

The next detail is that how do we know the word in the text have equivalent words? Here we can use a hash map to record all the words in the synonyms. We also want to know which group the words belong to. So the value of the hash map can be set as the group number.

See the code below:

class Solution {
public:
    vector<string> generateSentences(vector<vector<string>>& synonyms, string text) {
        int n = synonyms.size();
        vector<int> ps(n, 0);//initialization of parents in union-find
        for(int i=0; i<ps.size(); ++i) ps[i] = i;
        vector<int> visit(n, 0);
        for(int i=0; i<n; ++i) {//brute force to find groups
            if(visit[i]) continue;
            for(auto &a : synonyms[i]) {
                for(int j= i+1; j<n; ++j) {
                    if(visit[j]) continue;
                    for(auto &b : synonyms[j]) {
                        if(a == b) {ps[j] = i; visit[j] = 1; break;}
                    }
                }
            }
        }
        unordered_map<int, set<string>> mp1;
        unordered_map<string, int> mp2;
        for(int i=0; i<n; ++i) {
            for(auto &a : synonyms[i]) {
                mp1[ps[i]].insert(a);
                mp2[a] = ps[i];
            }
        }
        vector<string> res;
        dfs(text, 0, "", mp2, mp1, res);
        sort(res.begin(), res.end());
        return res;
    }

private:
    void dfs(string s, int idx, string one, unordered_map<string, int>& mp2, unordered_map<int, set<string>>& mp1, vector<string>& res) {
        if(idx >= s.size()) {res.push_back(one); return;}
        int i = idx;
        while(i<s.size() && s[i] != ' ') ++i;
        string t = s.substr(idx, i-idx);
        if(!mp2.count(t)) {
            if(i < s.size()) t.push_back(' ');
            return dfs(s, i+1, one+t, mp2, mp1, res);
        }
        int r = mp2[t];
        for(auto a = mp1[r].begin(); a != mp1[r].end(); ++a) {
            string t1 = *a;
            if(i<s.size()) t1.push_back(' ');
            dfs(s, i+1, one + t1, mp2, mp1, res);
        }
    }
};

Thursday, October 31, 2019

Leetcode 952: Largest Component Size by Common Factor

https://leetcode.com/problems/largest-component-size-by-common-factor/description/


Notes:

This question is apparently can be solved by Union-Find algorithm, but the key how to use it.

In order to find the connected components, we need to figure out the relationships which make the components connected.

For example, 2, 3, 6.

Without 6, 2 and 3 are not connected. However, 6 connects with both 2 and 3, thus all of them are connected.

Another way to say this is that 6 is connected with all of its factors (> 1).

So this gives us one of key observations: we can set up connections of A[i] with all its factors. Then we just need to count which group has the largest count.

See the code below:

class UF{
private:
    vector<int> ps;
public:
    UF(int n): ps(n) {
        for(int i=i; i<n; ++i) ps[i] = i;
    }
    int find(int i) {
        if(ps[i] != i) ps[i] = find(ps[i]);
        return ps[i];
    }
    void unite(int i, int j) {
        int a = find(i), b = find(j);
        if(a  != b) {
            ps[a] = b;
        }
    }
};

class Solution {
public:
    int largestComponentSize(vector<int>& A) {
        int m = *max_element(A.begin(), A.end());
        UF uf(m+1);
        for(auto &a : A) {
            for(int k=2; k*k<=a; ++k) {
                if(a % k == 0) {
                    uf.unite(a, k);
                    uf.unite(a, a/k);
                }
            }
        }
        int res = 1;
        unordered_map<int, int> mp;
        for(auto &a : A) {
            res = max(res, ++mp[uf.find(a)]);
        }
        return res;
    }
};

Monday, September 23, 2019

Leetcode 1202: Smallest String With Swaps

https://leetcode.com/problems/smallest-string-with-swaps/description/


Notes:

The key to this question is to understand what it means!!!

After thinking for a while, it basically says that we can swap any letters at that two positions if they can be swapped. Then, there maybe more than 2 positions correlated. Since we can swap them unlimitedly, which means we can always reach the lexicographical order of the letters correlated.

Or, if the letters belongs to a group (which means correlated), then we just need to sort them directly, then place them back in order to the previous positions. So the key is to determine the groups.

To this problem, union-find algorithm is a good choice. Using this algorithm, we can easily determine how many groups in a graph.

For this question, we need to move one step more. We need to figure out for each group, what are the elements? It seems not easy, but actually it is.  After running the union-find, we have built the union-find map for all the elements. We just need to check the root of each element, then we can easily print out the elements of each group (the root of each group is unique).

For union-find, it is almost O(1) in time to get the root of each elements. So it is fast.

See the code below:

class Solution {
public:
    string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
        int len = s.size();
        vector<vector<int>> mp(len);
        vector<int> rs(len, 0);
        for(int i=0; i<len; ++i) rs[i] = i;
        for(auto &a : pairs) {
            int x = find(a[0], rs), y = find(a[1], rs);
            if(x != y) rs[y] = x;
        }
        for(int i=0; i<len; ++i) mp[find(i, rs)].push_back(i);
        for(auto &a : mp) {
            string ss = "";
            for(auto &b : a) ss.push_back(s[b]);
            sort(ss.begin(), ss.end());
            for(int i=0; i<a.size(); ++i) s[a[i]] = ss[i];
        }
        return s;
    }

private:
    int find(int x, vector<int> &rs) {
        if(rs[x] != x) rs[x] = find(rs[x], rs);
        return rs[x];
    }
};

Sunday, September 15, 2019

Leetcode 839: Similar String Groups

https://leetcode.com/problems/similar-string-groups/description/



Notes:

This question seems very straightforward, but actually there is situation that we need to consider: when two group has a "bridge" which can connects two groups together, two groups merges into one. For this kind of problem, union-find algorithm is the first choice.

See the code below:

class Solution {
public:
    int numSimilarGroups(vector<string>& A) {
        int res = A.size(), len = res;
        vector<int> lb(len, 0);
        for(int i=1; i<len; ++i) lb[i] = i;//initial roots;
        for(int i=1; i<len; ++i) {
            for(int j=0; j<i; ++j) {
                if(isV(A[i], A[j])) {
                    int a = find(i, lb), b = find(j, lb);
                    if(a != b) {//belong to two groups
                        lb[a] = b;//becomes one group
                        --res;
                    }
                }
            }
        }
        return res;
    }
private:
    bool isV(string &s1, string &s2) {//can be similar
        int ct = 0;
        for(int i=0; i<s1.size(); ++i) {
            if(ct > 2) return false;
            if(s1[i] != s2[i]) ++ct;
        }
        return ct == 2 || ct == 0;
    }
    int find(int i, vector<int> &lb) {//find root
        if(lb[i] != i) lb[i] = find(lb[i], lb);//
        return lb[i];
    }
};

Saturday, August 24, 2019

Leetcode 1168: Optimize Water Distribution in a Village

https://leetcode.com/problems/optimize-water-distribution-in-a-village/description/


Notes:

This is another question about minimum spanning tree (MST). But we need to convert "digging wells" into "connections": for example, connections to well 0. Thus, it is become the same as Leetcode 1135: Connecting Cities With Minimum Cost 

The key point is it will use the standard union-find method. And the algorithm for MST is Kruskal's algorithm which is in a greedy fashion.

See the code below:

class Solution {
public:
    int minCostToSupplyWater(int n, vector<int>& wells, vector<vector<int>>& pipes) {
        int res = 0;
        for(int i=1; i<=n; ++i) pipes.push_back({0, i, wells[i-1]});
        sort(pipes.begin(), pipes.end(), [](auto &a, auto &b) {return a[2] < b[2];});
        vector<int> f(n+1, 0);
        for(int i=0; i<f.size(); ++i) f[i] = i;
        int ct = 0;
        for(auto &p : pipes) {
            int a = find(f, p[0]), b = find(f, p[1]);
            if(a != b) {
                f[b] = a;
                res += p[2];
                ++ct;
                if(ct==n) break;
            }
        }
        return res;
    }
private:
    int find(vector<int> &f, int x) {
        if(f[x] != x) f[x] = find(f, f[x]);
        return f[x];
    }
};


Here is another implementation using object-oriented design model. See the code below:

class unite_find {
private:
    vector<int> rt;
public:
    unite_find(int x) { //constructor
        for(int i=0; i<x; ++i) rt.push_back(i);
    }
   
    ~unite_find() {};//destructor

    int find(int x) {
        if(rt[x] != x) rt[x] = find(rt[x]);
        return rt[x];
    }

    void unite(int x, int y) {
        rt[x] = y;
    }
};

class Solution {
public:
    int minCostToSupplyWater(int n, vector<int>& wells, vector<vector<int>>& pipes) {
        int res = 0;
        for(int i=1; i<=n; ++i) pipes.push_back({0, i, wells[i-1]});
        sort(pipes.begin(), pipes.end(), [](const auto &a, const auto &b) {return a[2] < b[2];});
        unite_find uf(n+1);
        for(auto &p : pipes) {
            int a = uf.find(p[0]), b = uf.find(p[1]);
            if(a != b) {
                uf.unite(a, b);
                res += p[2];
            }
        }
        return res;
    }
};

Saturday, July 27, 2019

Leetcode 1135. Connecting Cities With Minimum Cost

https://leetcode.com/problems/connecting-cities-with-minimum-cost/description/

Description:

There are N cities numbered from 1 to N.
You are given connections, where each connections[i] = [city1, city2, cost] represents the cost to connect city1 and city2 together. (A connection is bidirectional: connecting city1 and city2 is the same as connecting city2 and city1.)
Return the minimum cost so that for every pair of cities, there exists a path of connections (possibly of length 1) that connects those two cities together. The cost is the sum of the connection costs used. If the task is impossible, return -1.

Notes:

This question asks for the weights of the minimum spanning tree. There are very good resource online to this topic. For example,

https://www.geeksforgeeks.org/kruskals-minimum-spanning-tree-algorithm-greedy-algo-2/

The key algorithm involved is the so called union-find algorithm, which can be find in the below link,

https://www.geeksforgeeks.org/union-find/

See the code below:

class Solution {
public:
    int minimumCost(int N, vector<vector<int>>& conections) {
        sort(conections.begin(), conections.end(), [](auto &a, auto &b){return a[2] < b[2];});
        vector<int> ps(N+1, 0);
        for(int i=1; i<=N; ++i) ps[i] = i;
        int res = 0, ct = 1;
        for(auto &a : conections) {
            int x = find(a[0], ps), y = find(a[1], ps);
            if(x != y) {
            ps[x] = y;
            res += a[2];
            ++ct;
            if(ct==N) return res;
            }
        }
        return -1;
    }
private:
    int find(int x, vector<int> &ps) {
        if(ps[x] != x) ps[x] = find(ps[x], ps);
        return ps[x];
    }
};