diff --git a/695. Max Area of Island.md b/695. Max Area of Island.md new file mode 100644 index 0000000..d625c43 --- /dev/null +++ b/695. Max Area of Island.md @@ -0,0 +1,475 @@ +# step 1 + +最初以下のように書いてエラー。 +Error case: [[1,1,0,0,0],[1,1,0,0,0],[0,0,0,1,1],[0,0,0,1,1]] + +複数方向から同じ位置に至る場合に余計に数えてしまう。 +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + WATER = 0 + + def measure_island_area( + visited: List[List[bool]], + row: int, + column: int + ) -> int: + area = 0 + directions = ( + (1, 0), + (0, 1), + (-1, 0), + (0, -1), + ) + next_positions = [(row, column)] + while next_positions: + row, column = next_positions.pop() + visited[row][column] = True + area += 1 + for delta_row, delta_column in directions: + next_row = row + delta_row + next_column = column + delta_column + if ( + not 0 <= next_row < NUM_ROW + or not 0 <= next_column < NUM_COLUMN + ): + continue + if grid[next_row][next_column] == WATER: + continue + if visited[next_row][next_column]: + continue + next_positions.append((next_row, next_column)) + return area + + max_area = 0 + visited = [[False] * NUM_COLUMN for _ in range(NUM_ROW)] + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == WATER: + continue + if visited[row][column]: + continue + max_area = max( + max_area, + measure_island_area(visited, row, column) + ) + return max_area +``` + +`if visited[row][column]`の確認位置を追加し修正。 +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + WATER = 0 + + def compute_island_area( + visited: List[List[bool]], + row: int, + column: int + ) -> int: + directions = ( + (1, 0), + (0, 1), + (-1, 0), + (0, -1), + ) + next_positions = [(row, column)] + area = 0 + while next_positions: + row, column = next_positions.pop() + if visited[row][column]: + continue + visited[row][column] = True + area += 1 + for delta_row, delta_column in directions: + next_row = row + delta_row + next_column = column + delta_column + if not ( + 0 <= next_row < NUM_ROW + and 0 <= next_column < NUM_COLUMN + ): + continue + if grid[next_row][next_column] == WATER: + continue + if visited[next_row][next_column]: + continue + next_positions.append((next_row, next_column)) + return area + + max_area = 0 + visited = [[False] * NUM_COLUMN for _ in range(NUM_ROW)] + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == WATER: + continue + if visited[row][column]: + continue + area = compute_island_area(visited, row, column) + max_area = max(max_area, area) + return max_area +``` + +UnionFindで要素数記録 +```python +class UnionFind: + def __init__(self, elements): + self.count = len(elements) + self.parent = {element: element for element in elements} + self.rank = {element: 1 for element in elements} + self.size = {element: 1 for element in elements} + + def find(self, element): + while element != self.parent[element]: + self.parent[element] = self.parent[self.parent[element]] + element = self.parent[element] + return element + + def union(self, element1, element2): + root1 = self.find(element1) + root2 = self.find(element2) + + if root1 == root2: + return + if self.rank[root1] > self.rank[root2]: + self.parent[root2] = root1 + self.size[root1] += self.size[root2] + elif self.rank[root1] < self.rank[root2]: + self.parent[root1] = root2 + self.size[root2] += self.size[root1] + else: + self.parent[root1] = root2 + self.size[root2] += self.size[root1] + self.rank[root1] += 1 + + def compute_maxsize(self): + max_size = 0 + for size in self.size.values(): + max_size = max(max_size, size) + return max_size + + +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + LAND = 1 + + lands = set() + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == LAND: + lands.add((row, column)) + + uf = UnionFind(lands) + for row, column in lands: + if (row + 1, column) in lands: + uf.union((row, column), (row + 1, column)) + if (row, column + 1) in lands: + uf.union((row, column), (row, column + 1)) + + return uf.compute_maxsize() +``` + +再帰する解法 +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + WATER = 0 + + def compute_island_area( + visited: List[List[bool]], + row: int, + column: int + ) -> int: + if not (0 <= row < NUM_ROW and 0 <= column < NUM_COLUMN): + return 0 + if grid[row][column] == WATER: + return 0 + if visited[row][column]: + return 0 + directions = ( + (1, 0), + (0, 1), + (-1, 0), + (0, -1), + ) + visited[row][column] = True + area = 1 + for delta_row, delta_column in directions: + next_row = row + delta_row + next_column = column + delta_column + area += compute_island_area(visited, next_row, next_column) + return area + + visited = [[False] * NUM_COLUMN for _ in range(NUM_ROW)] + max_area = 0 + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == WATER: + continue + if visited[row][column]: + continue + area = compute_island_area(visited, row, column) + max_area = max(max_area, area) + return max_area +``` + +上の`compute_island_area()`に関しては、以下の書き方と迷った。visitedかどうかのチェックは +関数冒頭に持っていく必要があったため、なら諸々のチェックはまとめてしまおうと思い、上のように書いた。 +```python +def compute_island_area( + visited: List[List[bool]], + row: int, + column: int +) -> int: + if visited[row][column]: + return 0 + directions = ( + (1, 0), + (0, 1), + (-1, 0), + (0, -1), + ) + visited[row][column] = True + area = 1 + for delta_row, delta_column in directions: + next_row = row + delta_row + next_column = column + delta_column + if not ( + 0 <= next_row < NUM_ROW and 0 <= next_column < NUM_COLUMN + ): + continue + if grid[next_row][next_column] == WATER: + continue + if visited[next_row][next_column]: + continue + area += compute_island_area(visited, next_row, next_column) + return area +``` + +# step 2 +- https://discord.com/channels/1084280443945353267/1200089668901937312/1222078986759311410 + - > 理想的な話をすると、関数は中を見なくても戻って来る物の想像がつくのが理想 +- https://github.com/olsen-blue/Arai60/pull/18/files#r1934079217 + - > visitedをsetにして、タプルなどで管理する選択肢もありますね + - hashの計算に時間がかかりそうなのと、landがたくさんある大きい入力だとメモリをたくさん使いそう + だが、必ずしもgridと同じ大きさのマップを用意する必要はないのか +- https://github.com/katataku/leetcode/pull/17/files + - 定数、seen,count_area関数定義,maxAreaOfIsland本体の定義という順番だった。 + - 自分は、seenに相当するvisitedをcount_area関数定義の下に入れていたので、count_areaの引数に + visitedを入れる必要があったが、`count_area(row, column)`の方が、 + 「(row, column)の場所を含む島の大きさを返すんだな」とわかりやすかった。 +- https://github.com/colorbox/leetcode/pull/32/files#r1898178545 + - > 範囲チェックをして追加という、同じ処理が繰り返されるので関数化をしたりラムダにしたりするのがいいでしょう。 +- https://github.com/tarinaihitori/leetcode/pull/18/files#r1851713519 + - > 破壊的というだけでなく、個人的にVISITEDはLANDかWATERとは別の次元の話だと思っており + - リストに入る要素同士の性質が意味的に並ぶことも重要なのか + +```python +area = compute_island_area(visited, row, column) +max_area = max(max_area, area) +``` +を`max_area = max(max_area, compute_island_area(visited, row, column))`としているものもあった。 +今見ている島の大きさを数えるのと、これまで見てきた島の最大の大きさを更新することは別の処理なので、 +分けたがこれくらいならまとめても良さそう。 + +get_island_areaだとgetterと誤解されそうなので、compute_island_areaとしたが、computeの要素が +そんなにない(computeの捉え方次第)ので、measure_island_areaとかcount_areaとかが良いと感じた。 + +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + WATER = 0 + visited = [[False] * NUM_COLUMN for _ in range(NUM_ROW)] + + def count_area(row: int, column: int) -> int: + area = 0 + next_positions = [(row, column)] + while next_positions: + row, column = next_positions.pop() + if not ( + 0 <= row < NUM_ROW + and 0 <= column < NUM_COLUMN + ): + continue + if grid[row][column] == WATER: + continue + if visited[row][column]: + continue + visited[row][column] = True + area += 1 + next_positions.append((row + 1, column)) + next_positions.append((row - 1, column)) + next_positions.append((row, column + 1)) + next_positions.append((row, column - 1)) + return area + + max_area = 0 + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == WATER: + continue + if visited[row][column]: + continue + area = count_area(row, column) + max_area = max(max_area, area) + return max_area +``` + +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + WATER = 0 + visited = [[False] * NUM_COLUMN for _ in range(NUM_ROW)] + + def count_area(row: int, column: int) -> int: + if not ( + 0 <= row < NUM_ROW + and 0 <= column < NUM_COLUMN + ): + return 0 + if grid[row][column] == WATER: + return 0 + if visited[row][column]: + return 0 + visited[row][column] = True + return ( + 1 + + count_area(row + 1, column) + + count_area(row - 1, column) + + count_area(row, column + 1) + + count_area(row, column - 1) + ) + + max_area = 0 + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == WATER: + continue + if visited[row][column]: + continue + area = count_area(row, column) + max_area = max(max_area, area) + return max_area +``` + +諸々のチェックを、stackを使ったDFSだとstackに追加する前かpopした後か、 +再帰DFSだと呼び出し直前か直後か、queueを使ったBFSでもenqueueの前か後か、選択肢がある。が、 +結局あるgridに至るまでのルートが複数あるため、visitedをかきかえる直前にもう書き変わっているかの +チェックをする必要がある。であるなら、stackをつかったDFSだったらpop後、再帰DFSだと呼び出し直後、 +queueをつかったBFSだとdequeのあとにすると似たようなチェックを一箇所にまとめられる。 +まとめた方が綺麗だが、データの追加や再帰の回数が増える。 +まとめて綺麗に描こうとするのなら、再帰の最大数が今回だとO(m * n)で2500となるので、 +再帰は避けたくなった。 + +```python +class UnionFind: + def __init__(self, elements): + self.count = len(elements) + self.parent = {element: element for element in elements} + self.rank = {element: 1 for element in elements} + self.size = {element: 1 for element in elements} + + def find(self, element): + while element != self.parent[element]: + self.parent[element] = self.parent[self.parent[element]] + element = self.parent[element] + return element + + def union(self, element1, element2): + root1 = self.find(element1) + root2 = self.find(element2) + + if root1 == root2: + return + if self.rank[root1] < self.rank[root2]: + self.parent[root1] = root2 + self.size[root2] += self.size[root1] + elif self.rank[root1] > self.rank[root2]: + self.parent[root2] = root1 + self.size[root1] += self.size[root2] + else: + self.parent[root1] = root2 + self.rank[root2] += 1 + self.size[root2] += self.size[root1] + + def compute_maxsize(self) -> int: + maxsize = 0 + for size in self.size.values(): + maxsize = max(maxsize, size) + return maxsize + +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + WATER = 0 + lands = set() + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if grid[row][column] == WATER: + continue + lands.add((row, column)) + + uf = UnionFind(lands) + for row, column in lands: + if (row + 1, column) in lands: + uf.union((row, column), (row + 1, column)) + if (row, column + 1) in lands: + uf.union((row, column), (row, column + 1)) + return uf.compute_maxsize() +``` + +# step 3 + +```python +class Solution: + def maxAreaOfIsland(self, grid: List[List[int]]) -> int: + NUM_ROW = len(grid) + NUM_COLUMN = len(grid[0]) + visited = [[False] * NUM_COLUMN for _ in range(NUM_ROW)] + + def is_explorable(row: int, column: int) -> bool: + WATER = 0 + if not ( + 0 <= row < NUM_ROW + and 0 <= column < NUM_COLUMN + ): + return False + if grid[row][column] == WATER: + return False + return not visited[row][column] + + def count_area(row: int, column: int) -> int: + area = 0 + next_positions = [(row, column)] + while next_positions: + row, column = next_positions.pop() + if not is_explorable(row, column): + continue + visited[row][column] = True + area += 1 + next_positions.append((row + 1, column)) + next_positions.append((row - 1, column)) + next_positions.append((row, column + 1)) + next_positions.append((row, column - 1)) + return area + + max_area = 0 + for row in range(NUM_ROW): + for column in range(NUM_COLUMN): + if is_explorable(row, column): + area = count_area(row, column) + max_area = max(max_area, area) + return max_area +``` \ No newline at end of file