lc200.岛屿数量

 

题目链接

啊这个题之前有看见过不过那个时候还是很早的时候没什么思路,然后就看了一眼别人的思路,原来就是一个状态空间搜索而已,就用递归写出来了,虽然感觉好像写得没有别人的简洁(见实现2)。用深度优先遍历的时间复杂度是O(MN),空间复杂度是O(MN)(我也不知道多少,如果考虑是在原先的grid上更改的话应该是O(1)?)。当然,更自然的,我们也可以使用广度优先遍历算法,如实现3所示,广度优先遍历的时间复杂度为O(MN),空间复杂度O(min(M,N)),在最欢情况下,整个网格均为陆地,队列的大小可以达到min(M,N)。

最近发现自己不太会做并查集相关的题目,在复习,然后来写一下关于这个题的新做法,即用并查集代替搜索。为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为1,则将其与相邻四个方向上的1在并查集中进行合并。最终岛屿的数量就是并查集中连通分量的数目。关于并查集相关的算法循序渐进的分别有quick-find,quick-union,weighted quick-union,weighted quick-union with path compression。quick-find时间复杂度高,就先不管了。我想首先必须要掌握weighted quick-union,如实现4所示。这样做的话算法中的操作的时间复杂度分别是initialize为N,union为lgN,connected也为lgN。接着,加上更进一步的优化,维护一个额外数组sz,来记录每个根节点对应树的节点数目,在union时比较p和q所在树的节点数目,将节点数量较少的树合并到较大的树上。然后我又在这里写了最终优化完的weighted QU with path compression(如实现3),其中再find函数里使用了路径压缩,并在rank数组上使用了按秩合并,将单次操作的时间复杂度降为了alpha(MN),其中alpha为反阿克曼函数,当自变量在人类可观测范围内(宇宙中的粒子数量)时,函数alpha(x)的值不会超过5,因此也可以看作常数时间复杂度,另外空间复杂度为O(MN)。

最后关于优化quick-union的一点小讨论:关于树的深度,普林斯顿的algorithm课程中提出了一个结论,按照Weighted QU 中的合并方式,任意一个节点的深度不会超过lgN(以2为底),因为每个节点深度加1都需要另一个树的节点数目超过节点所属树的节点数目,因此合并后,该节点所属树的节点数目至少是原来的2倍。从初始所在树的数目为1(只有自身)到最终数目为N(全部节点都属于一棵树),最多只能有lgN次double,深度最多也只能从0增加到lgN。而路径压缩则是在每次查找根节点的时候,可以顺便将子节点的父节点修改为更上一层的节点,如父节点的父节点,这样可以使树的结构变得更扁平。

class Solution:
    def numIslands1(self, grid: List[List[str]]) -> int:
        self.dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]
        self.grid = grid
        cnt = 0
        self.lrow, self.lcol = len(grid), len(grid[0])
        for i in range(self.lrow):
            for j in range(self.lcol):
                pos = (i, j)
                if self.judgeIsland(pos):
                    cnt += 1
                    self.clearIsland(pos)
        return cnt
    
    def clearIsland(self, pos):
        self.grid[pos[0]][pos[1]] = "0"
        for d in self.dirs:
            neigbour = (pos[0]+d[0], pos[1]+d[1])
            if 0 <= neigbour[0] < self.lrow and 0 <= neigbour[1] < self.lcol:
                if self.judgeIsland(neigbour):
                    self.clearIsland(neigbour)

    def judgeIsland(self, pos):
        return self.grid[pos[0]][pos[1]] == "1"

    def numIslands2(self, grid: [[str]]) -> int:
        def dfs(grid, i, j):
            if not 0 <= i < len(grid) or not 0 <= j < len(grid[0]) or grid[i][j] == '0': return
            grid[i][j] = '0'
            dfs(grid, i + 1, j)
            dfs(grid, i, j + 1)
            dfs(grid, i - 1, j)
            dfs(grid, i, j - 1)
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    dfs(grid, i, j)
                    count += 1
        return count

class UnionFind:
    def __init__(self, grid):
        m, n = len(grid), len(grid[0])
        self.count = 0
        self.parent = [-1] * (m * n)
        self.rank = [0] * (m * n)
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '1':
                    self.parent[i * n + j] = i * n + j
                    self.count += 1
    
    def find(self, i):
        if self.parent[i] != i:
            self.parent[i] = self.find(self.parent[i])
        return self.parent[i]
    
    def union(self, x, y):
        rootx = self.find(x)
        rooty = self.find(y)
        if rootx != rooty:
            if self.rank[rootx] < self.rank[rooty]:
                rootx, rooty = rooty, rootx
            self.parent[rooty] = rootx
            if self.rank[rootx] == self.rank[rooty]:
                self.rank[rootx] += 1
            self.count -= 1
    
    def getCount(self):
        return self.count


class Solution:
    def numIslands3(self, grid: List[List[str]]) -> int:
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        uf = UnionFind(grid)
        num_islands = 0
        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == '1':
                    grid[r][c] = '0'
                    for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
                        if 0 <= x < nr and 0 <= y < nc and grid[x][y] == '1':
                            uf.union(r * nc + c, x * nc + y)
        
        return uf.getCount()

class Solution:
    def numIslands4(self, grid: List[List[str]]) -> int:
        f = {}
        def find(x):
            f.setdefault(x, x)
            if f[x] != x:
                f[x] = find(f[x])
            return f[x]
        
        def union(x, y):
            f[find(x)] = find(y)

        if not grid:
            return 0
        row = len(grid)
        col = len(grid[0])

        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":
                    for x, y in [[-1, 0], [0, -1]]:
                        tmp_i = i + x
                        tmp_j = j + y
                        if 0 <= tmp_i < row and 0 <= tmp_j < col and grid[tmp_i][tmp_j] == "1":
                            union(tmp_i * row + tmp_j, i * row + j)
        
        res = set()
        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":
                    res.add(find(i * row + j))
        return len(res)