Skip to content

Conversation

@altay9
Copy link
Collaborator

@altay9 altay9 commented Jun 17, 2021

Resolves: #234

When we add a new land; in some cases, it can decrease the total number of islands.
Because it can connect two distinct islands as a united component.

This reminds a quote by @ErdemT09, in the PR #200:

It is important to understand that:
If a new given edge connects 2 previously discrete nodes, then the number of connected components decreases.

It means, this question can be interpreted as a variant of #200 and we can use Union-find here as well.
We use almost the same code with #200.
This time, instead of edge pairs of nodes, we have x, y positions array.
Therefore, we need to generate edges list using these positions.
We can interpret each tile in the grid, as a node. We need a getNodeId method to convert a position into a node-id.
In this case, an edge is only possible between a node and its valid adjacent nodes.
It is why we need the DIRECTIONS array.

@altay9
Copy link
Collaborator Author

altay9 commented Jun 17, 2021

Notes:

1- LeetCode's own Union-Find solution has a bug and does not work.
We don't care. Their code was too complex already.

2- In our solution, we did not need to create a grid as we can use the Union-find's parent array (roots) as an imaginary grid.

@ErdemT09
Copy link
Collaborator

Die Programmlesbarkeit ist ziemlich gut. Es war mir leicht zu verstehen, auch aufgrund der Tatsache, dass das Programm für dessen Aufgabe einfach aufgebaut und moduliert ist.

@altay9
Copy link
Collaborator Author

altay9 commented Jun 17, 2021

Die Programmlesbarkeit ist ziemlich gut. Es war mir leicht zu verstehen, auch aufgrund der Tatsache, dass das Programm für dessen Aufgabe einfach aufgebaut und moduliert ist.

Es freut mich sehr, dass es dir gefällt, Erdem.
Der Code ist das Ergebnis der wertvollen Beiträge, die du in der vorherigen Frage geleistet hast.

@ErdemT09
Copy link
Collaborator

ErdemT09 commented Jun 17, 2021

I tried applying the rank system here, but it only made the code slower to 8 ms for some reason.

class Solution {
        public final static int[][] DIRECTIONS = { { 0, -1 }, { 0, 1 }, { -1, 0 }, { 1, 0 } };
    int m = 0, n = 0;
    int totalNode = 0;
    int roots[];
    int ranks[];

    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        List<Integer> output = new ArrayList<>();
        this.m = m;
        this.n = n;
        roots = new int[m * n + 1];
        ranks = new int[m * n + 1];

        for (int[] position : positions) {
            int nodeId = getNodeId(position[0], position[1]);
            if (roots[nodeId] != 0) {
                output.add(totalNode);
                continue;
            }

            roots[nodeId] = nodeId;
            ranks[nodeId] = 1;
            ++totalNode;
            countComponents(nodeId, generateNeigbors(nodeId, position));
            output.add(totalNode);
        }
        return output;
    }

    int getNodeId(int x, int y) {
        return n * x + y + 1;
    }

    final int INVALID_VERTEX = -1;

    int[] generateNeigbors(int nodeId, int[] position) {
        int[] neighbors = new int[4];
        for (int i = 0; i < DIRECTIONS.length; i++) {
            int x = position[0] + DIRECTIONS[i][0];
            int y = position[1] + DIRECTIONS[i][1];
            int adjacentNodeId = getNodeId(x, y);

            if (!isValidNode(x, y, adjacentNodeId)) {
                neighbors[i] = INVALID_VERTEX;
                continue;
            }
            neighbors[i] = adjacentNodeId;
        }
        return neighbors;
    }

    boolean isValidNode(int x, int y, int adjacentNodeId) {
        return (x >= 0 && x < m && y >= 0 && y < n && roots[adjacentNodeId] != 0);
    }

    public int countComponents(int nodeId, int[] neigbors) {

        for (int neighbor : neigbors) {

            if (neighbor == INVALID_VERTEX) {
                continue;
            }
            int r1 = findRoot(nodeId);
            int r2 = findRoot(neighbor);

            if (r1 != r2) {
                if (ranks[r1] <= ranks[r2]) {
                    roots[r1] = r2;
                    ranks[r2] += ranks[r1];
                } else {
                    roots[r2] = r1;
                    ranks[r1] += ranks[r2];
                }
                totalNode--;
            }
        }

        return totalNode;
    }

    private int findRoot(int key) {
        if (key != roots[key]) {
            roots[key] = roots[roots[key]];
            key = roots[key];
            return findRoot(key);
        } else {
            return key;
        }
    }
}

@ErdemT09
Copy link
Collaborator

ErdemT09 commented Jun 17, 2021

I've found this solution that runs in 5ms. It does exactly the same things that the ranks solution I've commented above does, seemingly it should be even slower because of its extra calculations and usage of classes and such. It is unfortunately a bit illegible.

class Solution {
    public List<Integer> numIslands2(int m, int n, int[][] positions) {
        List<Integer> ans = new ArrayList<>();
        
        UnionFind uf = new UnionFind(m, n);
        for (int[] position : positions) {
            ans.add(uf.connect(position[0], position[1]));
        }
                    
        return ans;
    }
    
    class UnionFind {
        int sets;
        int[] parent;
        int[] size;
        int col;
        int row;
        // int[] help;
        
        public UnionFind (int m, int n) {
            int len = m * n;
            parent = new int[len];
            size = new int[len];
            col = n;
            row = m;
            sets = 0;
        }
        
        private int index(int r, int c) {
            return r * col + c;
        }
        
        public int find(int i) {
            if (parent[i] != i) {
                parent[i] = find(parent[i]);
            }
            
            return parent[i];
        }
        
        private void union (int r1, int c1, int r2, int c2) {
            if (r1 < 0 || r1 == row || c1 < 0 || c1 == col || r2 < 0 || r2 == row || c2 < 0 || c2 == col) {
                return;
            }
            int i1 = index(r1, c1);
            int i2 = index(r2, c2);

            if (size[i1] == 0 || size[i2] == 0) {
                return;
            }

            int f1 = find(i1);
            int f2 = find(i2);
            
            if (f1 != f2) {
                if (size[f1] >= size[f2]) {
                    size[f1] += size[f2];
                    parent[f2] = f1;
                } else {
                    size[f2] += size[f1];
                    parent[f1] = f2;
                }
                sets--;
            }
        }
        
        public int connect(int r, int c) {
            int index = index(r, c);
            if (size[index] == 0) {
                sets++;
                parent[index] = index;
                size[index] = 1;
                union(r - 1, c, r, c);
                union(r + 1, c, r, c);
                union(r, c - 1, r, c);
                union(r, c + 1, r, c);
            }
            
            return sets();
        }
        
        private int sets() {
            return sets;
        }
    }
}

Co-authored-by: ErdemT09 <63192680+ErdemT09@users.noreply.github.com>
@ErdemT09
Copy link
Collaborator

I tried applying the rank system here, but it only made the code slower to 8 ms for some reason.

I was going to say: It seems logically more efficient as we would create tree like faster paths under the node with higher rank. I think we should disregard the tests of LeetCode and actually add this.

Creating an entire array that has as many elements as the total grid size might be perhaps slow. The Union class solutions does this too though. Whether to add this rank solution is remains at your decision then @altayhunoglu .

@altay9
Copy link
Collaborator Author

altay9 commented Jun 17, 2021

I tried applying the rank system here, but it only made the code slower to 8 ms for some reason.

I was going to say: It seems logically more efficient as we would create tree like faster paths under the node with higher rank. I think we should disregard the tests of LeetCode and actually add this.

Creating an entire array that has as many elements as the total grid size might be perhaps slow. The Union class solutions does this too though. Whether to add this rank solution is remains at your decision then @altayhunoglu .

Of course, we can add Erdem. Thanks for your help and nice idea about ranking.

Fixes by Erdem:
-adds ranking, formats code, renames methods with a more meaningful approach.
@ErdemT09
Copy link
Collaborator

Inverting the rank condition still leads to 8ms. Interesting.
image

Copy link
Collaborator

@ErdemT09 ErdemT09 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem itself is just another example of the "Union-find" problems. Even though I haven't really taken part in jobs and such, the code here is I think some fine example of production-business code. Easy to debug, easy to modify, easy to understand etc.
Approved.

@altay9 altay9 merged commit 35d7bc0 into master Jun 17, 2021
@altay9 altay9 deleted the NumberofIslandsII branch June 17, 2021 16:54
@ErdemT09 ErdemT09 mentioned this pull request Jun 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

305. Number of Islands II

3 participants