In [1]:
1
2
3
4
5
import sys, random
sys.path.append("/home/swyoo/algorithm/")
from typing import List
import numpy as np
from utils.verbose import visualize_ds, logging_time

1254. Number of Closed Islands

Let $m, n$ be grid shape.

DFS + Disjoint Set(or Union Find)

Idea

  1. Remove lands connected to edges by re-marking grid elements.
  2. Union all grid except for water.
  3. Count all representatives.

The time complexity is $O(mn\alpha)$

In [2]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Solution1:
    @logging_time
    def closedIsland(self, grid: List[List[int]], show=False) -> int:
        par, rnk = {}, {}
        def find(x):
            if x not in par:
                par[x] = x
                rnk[x] = 0
                return par[x]
            if x != par[x]:
                par[x] = find(par[x])
            return par[x]
        def union(x, y):
            x, y = find(x), find(y)
            if x == y: return
            if rnk[x] > rnk[y]:
                x, y = y, x
            par[x] = y
            if rnk[x] == rnk[y]:
                rnk[y] += 1

        def adj(i, j):
            for x, y in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
                if 0 <= x < len(grid) and 0 <= y < len(grid[0]) and not grid[x][y]:
                    yield x, y

        seen = set()
        def rmland(i, j):
            seen.add((i, j))
            grid[i][j] = 1
            for x, y in adj(i, j):
                if (x, y) not in seen:
                    rmland(x, y)

        for i in range(len(grid)):
            if (i, 0) not in seen and not grid[i][0]:
                rmland(i, 0)
            if (i, len(grid[0]) - 1) not in seen and not grid[i][len(grid[0]) - 1]:
                rmland(i, len(grid[0]) - 1)

        for j in range(len(grid[0])):
            if (0, j) not in seen and not grid[0][j]:
                rmland(0, j)
            if (len(grid) - 1, j) not in seen and not grid[len(grid) - 1][j]:
                rmland(len(grid) - 1, j)

        if show: print(np.array(grid))

        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if not grid[i][j]:
                    find((i, j))
                    for x, y in adj(i, j):
                        union((i, j), (x, y))
        if show: visualize_ds(par)

        ans = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if not grid[i][j] and (i, j) == find((i,j)):
                    ans += 1
        return ans
    
sol1 = Solution1()
In [3]:
1
2
grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
sol1.closedIsland(grid, show=True, verbose=True)
1
2
3
4
5
6
[[1 1 1 1 1 1 1 1]
 [1 0 0 0 0 1 1 1]
 [1 0 1 0 1 1 1 1]
 [1 0 0 0 0 1 0 1]
 [1 1 1 1 1 1 1 1]]

png

1
2
WorkingTime[closedIsland]: 191.04004 ms

1
2

DFS

Idea

seen is very important!.

  1. Remove lands connected to edges by re-marking grid elements and add all grid to seen.
  2. add all lands to seen.

notice that once dfs(i, j) marks all lands connected to (i, j) except for waters.

In [4]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution2:
    @logging_time
    def closedIsland(self, grid: List[List[int]], show=False) -> int:

        def adj(i, j):
            for x, y in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
                if 0 <= x < len(grid) and 0 <= y < len(grid[0]) and not grid[x][y]:
                    yield x, y

        seen = set()
        def dfs(i, j, rmland=False):
            seen.add((i, j))
            if rmland: grid[i][j] = 1
            for x, y in adj(i, j):
                if (x, y) not in seen:
                    dfs(x, y)

        for i in range(len(grid)):
            if (i, 0) not in seen and not grid[i][0]:
                dfs(i, 0, rmland=True)
            if (i, len(grid[0]) - 1) not in seen and not grid[i][len(grid[0]) - 1]:
                dfs(i, len(grid[0]) - 1, rmland=True)

        for j in range(len(grid[0])):
            if (0, j) not in seen and not grid[0][j]:
                dfs(0, j, rmland=True)
            if (len(grid) - 1, j) not in seen and not grid[len(grid) - 1][j]:
                dfs(len(grid) - 1, j, rmland=True)

        if show: print(np.array(grid))

        ans = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if (i, j) not in seen and not grid[i][j]:
                    ans += 1
                    dfs(i, j)
        return ans

sol2 = Solution2()
In [5]:
1
2
3
m, n = 1000, 1000
grid = [[random.randint(0, 1) for i in range(m)] for j in range(n)] 
print(np.array(grid))
1
2
3
4
5
6
7
8
[[1 1 0 ... 1 0 0]
 [1 0 1 ... 1 1 0]
 [1 0 0 ... 0 1 1]
 ...
 [0 0 0 ... 0 1 1]
 [0 0 1 ... 1 0 0]
 [0 1 0 ... 1 0 1]]

In [6]:
1
2
3
4
ans1 = sol1.closedIsland(grid, verbose=True)
ans2 = sol2.closedIsland(grid, verbose=True)
print(ans1, ans2)
assert ans1 == ans2
1
2
3
4
WorkingTime[closedIsland]: 2841.18605 ms
WorkingTime[closedIsland]: 1092.00549 ms
64681 64681

Leave a comment