|
| 1 | +from collections import deque |
| 2 | +from typing import Generic, List, Optional, TypeVar |
| 3 | +from unittest import TestCase, main |
| 4 | + |
| 5 | + |
| 6 | +T = TypeVar('T') |
| 7 | + |
| 8 | + |
| 9 | +class Node(Generic[T]): |
| 10 | + def __init__(self, value: T): |
| 11 | + self.value = value |
| 12 | + self.prev = None |
| 13 | + self.post = None |
| 14 | + |
| 15 | + |
| 16 | +class Deque(Generic[T]): |
| 17 | + def __init__(self): |
| 18 | + self.head: Optional[Node[T]] = None |
| 19 | + self.tail: Optional[Node[T]] = None |
| 20 | + self.size = 0 |
| 21 | + |
| 22 | + def is_empty(self): |
| 23 | + return self.size == 0 |
| 24 | + |
| 25 | + def appendright(self, value: T): |
| 26 | + node = Node(value) |
| 27 | + if self.is_empty(): |
| 28 | + self.head = self.tail = node |
| 29 | + else: |
| 30 | + node.prev = self.tail |
| 31 | + if self.tail: |
| 32 | + self.tail.post = node |
| 33 | + self.tail = node |
| 34 | + self.size += 1 |
| 35 | + |
| 36 | + def appendleft(self, value: T): |
| 37 | + node = Node(value) |
| 38 | + if self.is_empty(): |
| 39 | + self.head = self.tail = node |
| 40 | + else: |
| 41 | + node.post = self.head |
| 42 | + if self.head: |
| 43 | + self.head.prev = node |
| 44 | + self.head = node |
| 45 | + self.size += 1 |
| 46 | + |
| 47 | + def popright(self) -> T: |
| 48 | + if self.is_empty(): |
| 49 | + raise IndexError("Deque is empty!") |
| 50 | + |
| 51 | + value = self.tail.value |
| 52 | + self.tail = self.tail.prev |
| 53 | + if self.tail: |
| 54 | + self.tail.post = None |
| 55 | + else: |
| 56 | + self.head = None |
| 57 | + self.size -= 1 |
| 58 | + return value |
| 59 | + |
| 60 | + def popleft(self) -> T: |
| 61 | + if self.is_empty(): |
| 62 | + raise IndexError("Deque is empty!") |
| 63 | + |
| 64 | + value = self.head.value |
| 65 | + self.head = self.head.post |
| 66 | + if self.head: |
| 67 | + self.head.prev = None |
| 68 | + else: |
| 69 | + self.tail = None |
| 70 | + self.size -= 1 |
| 71 | + return value |
| 72 | + |
| 73 | + |
| 74 | +class Solution: |
| 75 | + def numIslands(self, grid: List[List[str]]) -> int: |
| 76 | + return self.solve_bfs_custom(grid) |
| 77 | + |
| 78 | + """ |
| 79 | + Runtime: 243 ms (Beats 60.00%) |
| 80 | + Time Complexity: O(MAX_R * MAX_C) |
| 81 | + - 2차원 배열 grid를 조회하며 deq에 enqueue하는데 O(MAX_R * MAX_C) |
| 82 | + - deq의 원소 하나 당 DIRS 크기만큼 탐색에 O(4), 원소에 해당하는 grid의 값이 "0"인 경우 탐색하지 않으므로 upper bound |
| 83 | + - deque의 appendleft, popleft에 O(1) |
| 84 | + > O(MAX_R * MAX_C) * O(4) ~= O(MAX_R * MAX_C) |
| 85 | +
|
| 86 | + Memory: 18.82 (Beats 83.56%) |
| 87 | + Space Complexity: O(MAX_R * MAX_C) |
| 88 | + - visited 역할을 grid의 원소의 값을 변경하여 사용하였으므로 무시 |
| 89 | + - deque의 최대 크기는 MAX_R * MAX_C 이므로 O(MAX_R * MAX_C), upper bound |
| 90 | + > O(MAX_R * MAX_C) |
| 91 | + """ |
| 92 | + def solve_bfs(self, grid: List[List[str]]) -> int: |
| 93 | + |
| 94 | + def is_island(r: int, c: int) -> bool: |
| 95 | + nonlocal grid |
| 96 | + |
| 97 | + if grid[r][c] != "1": |
| 98 | + return False |
| 99 | + |
| 100 | + deq = deque([(r, c)]) |
| 101 | + deq.appendleft((r, c)) |
| 102 | + while deq: |
| 103 | + curr_r, curr_c = deq.popleft() |
| 104 | + grid[r][c] = "0" |
| 105 | + for dir_r, dir_c in DIRS: |
| 106 | + post_r, post_c = curr_r + dir_r, curr_c + dir_c |
| 107 | + if 0 <= post_r < MAX_R and 0 <= post_c < MAX_C and grid[post_r][post_c] == "1": |
| 108 | + grid[post_r][post_c] = "0" |
| 109 | + deq.appendleft((post_r, post_c)) |
| 110 | + |
| 111 | + return True |
| 112 | + |
| 113 | + MAX_R, MAX_C = len(grid), len(grid[0]) |
| 114 | + DIRS = ((-1, 0), (1, 0), (0, -1), (0, 1)) |
| 115 | + count = 0 |
| 116 | + for r in range(MAX_R): |
| 117 | + for c in range(MAX_C): |
| 118 | + count += 1 if is_island(r, c) else 0 |
| 119 | + return count |
| 120 | + |
| 121 | + """ |
| 122 | + Runtime: 283 ms (Beats 23.05%) |
| 123 | + Time Complexity: O(MAX_R * MAX_C) |
| 124 | + > solve_bfs와 동일 |
| 125 | + Memory: 19.98 (Beats 44.38%) |
| 126 | + Space Complexity: O(MAX_R * MAX_C) |
| 127 | + > solve_bfs와 동일 |
| 128 | + """ |
| 129 | + def solve_bfs_custom(self, grid: List[List[str]]) -> int: |
| 130 | + |
| 131 | + def is_island(r: int, c: int) -> bool: |
| 132 | + nonlocal grid |
| 133 | + |
| 134 | + if grid[r][c] != "1": |
| 135 | + return False |
| 136 | + |
| 137 | + deq = Deque() |
| 138 | + deq.appendleft((r, c)) |
| 139 | + while not deq.is_empty(): |
| 140 | + curr_r, curr_c = deq.popleft() |
| 141 | + grid[r][c] = "0" |
| 142 | + for dir_r, dir_c in DIRS: |
| 143 | + post_r, post_c = curr_r + dir_r, curr_c + dir_c |
| 144 | + if 0 <= post_r < MAX_R and 0 <= post_c < MAX_C and grid[post_r][post_c] == "1": |
| 145 | + grid[post_r][post_c] = "0" |
| 146 | + deq.appendleft((post_r, post_c)) |
| 147 | + |
| 148 | + return True |
| 149 | + |
| 150 | + MAX_R, MAX_C = len(grid), len(grid[0]) |
| 151 | + DIRS = ((-1, 0), (1, 0), (0, -1), (0, 1)) |
| 152 | + count = 0 |
| 153 | + for r in range(MAX_R): |
| 154 | + for c in range(MAX_C): |
| 155 | + count += 1 if is_island(r, c) else 0 |
| 156 | + return count |
| 157 | + |
| 158 | + |
| 159 | +class _LeetCodeTestCases(TestCase): |
| 160 | + def test_1(self): |
| 161 | + grid = [ |
| 162 | + ["1", "1", "0", "0", "0"], |
| 163 | + ["1", "1", "0", "0", "0"], |
| 164 | + ["0", "0", "1", "0", "0"], |
| 165 | + ["0", "0", "0", "1", "1"] |
| 166 | + ] |
| 167 | + output = 3 |
| 168 | + self.assertEqual(Solution.numIslands(Solution(), grid), output) |
| 169 | + |
| 170 | + |
| 171 | +if __name__ == '__main__': |
| 172 | + main() |
0 commit comments