🏠

Number Of Islands


https://leetcode.com/problems/number-of-islands

Since the grid is an undirected graph and we must find how many connected groups there are, this is a textbook case for UnionFind!

  • Add all lands
  • Union all connections
  • Count how many groups there are!

Algorithm

class Solution:
    # Time: O(n) because we iterate over the grid twice doing constant ops, and then O(n) set_count.
    # Space: O(n) because the union-find DS requires O(n) space.
    def numIslands(self, grid: List[List[str]]) -> int:
        uf = UnionFind()
        
        # Add all lands to the union-find DS.
        for y in range(len(grid)):
            for x in range(len(grid[0])):
                if grid[y][x] != "1":
                    continue
                uf.add((x, y))
        
        # Add all edges (connections) between lands to the union-find DS.
        # Note that we only need to check right & down. Can also connect left & up, but redundant.
        for y in range(len(grid)):
            for x in range(len(grid[0])):
                if grid[y][x] != "1":
                    continue
                for delta in [(1, 0), (0, 1)]:
                    dx, dy = delta
                    nx, ny = (x+dx, y+dy)
                    # Don't go out of bounds!
                    if ny >= len(grid) or nx >= len(grid[0]):
                        continue
                    if grid[ny][nx] != "1":
                        continue
                    uf.union((x, y), (nx, ny))
        
        # The number of connected groups is the number of islands!
        return uf.set_count()

class UnionFind: # Used to keep track of disjointed sets on graphs.
    parent: list[int] # Sets are trees. Each vertex has a parent.
    size: list[int] # Optimization: on union, larger set becomes parent.
    idx: dict[any, int] # Optional: elements are mapped to int "labels".

    def __init__(self):
        self.parent = []
        self.size = []
        self.idx = {}

    # Add a new vertex to the forest, as a new tree of size 1
    def add(self, vertex: any):
        idx = len(self.parent) # Next available idx to use for this vertex.
        self.idx[vertex] = idx
        self.parent.append(idx) # New vertex is its own parent in own tree.
        self.size.append(1) # Tree of one vertex: size = 1

    # Union two vertices: if they belong to != sets, make larger parent
    # of the smaller. Keep track of sizes.
    # vertices must exist (via add)!
    def union(self, vertex1: any, vertex2: any):
        set1 = self.find(vertex1)
        set2 = self.find(vertex2)
        if set1 != set2:
            if self.size[set1] > self.size[set2]:
                self.size[set1] += self.size[set2]
                self.parent[set2] = set1
            else:
                self.size[set2] += self.size[set1]
                self.parent[set1] = set2

    def _find(self, idx: int) -> int:
        return self._find(self.parent[idx]) if self.parent[idx] != idx else idx
    
    # Find the set a vertex belongs to.
    # vertex must exist (via add)!
    def find(self, vertex: any) -> int:
        return self._find(self.idx[vertex])
    
    # Count how many different sets exist (O(n))
    def set_count(self) -> int:
        return sum(1 for idx, parent in enumerate(self.parent) if idx == parent)

If you have trouble remembering how to implement UnionFind, there’s a simple DFS solution too:

  • For every cell, if it’s land, +1 to the total number of islands, and then BURN connected islands.
  • BURN: dfs via adjacent cells and replace the “1” to “X” or whatever.
  • Note that the “burning” plays the role of the “visited” set, so you don’t stack overflow.

 

Issues & PRs welcome ♥️
Powered by Hugo - Theme beautifulhugo