Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 294 additions & 0 deletions max-area-of-island/main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
## Step1
Number of Islandsと同じようにDFSで解く. 前問とは異なり, 同じノードを2回数えてしまうと答えがずれるのでvisitedやstackへの格納を正確に行うことが重要.
時間計算量: O(M ✕ N)
```py
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0

m = len(grid)
n = len(grid[0])
Comment on lines +10 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m, nだと変数の中身がわからなくなるので,num_row, num_colなどにする方が好みです.

LAND = 1
WATER = 0
visited = set()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

girdと同じサイズの2D配列でも良いですね。

directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には[(1, 0), (-1, 0), (0, 1), (0, -1)]などの順番の方が意図が明確に感じます。好みの範囲かもしれません。

def calculate_area_of_island(x, y):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

グリッドを辿って面積を数えることをcalculateと表現するのは若干違和感があります。explore_island, traverse_islandとかはどうでしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

面積なので個人的には area があって欲しいですね。measure あたりですかねえ。calculate, compute あたりも私はありだと思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x, yで座標のように指定していますが,リストの中にリストを入れて2次元を表現する際にはgrid[x][y]xが縦軸,yが横軸を指定するように対応させるケースが多いと思うので,混同しないようにコメントを入れても良いかもしれません.
つまり,

0 0 1 0
1 1 0 0
1 0 0 1

という縦3,横4のマップに対して

grid = [[0, 0, 1, 0],
        [1, 1, 0, 0],
        [1, 0, 0, 1]]

と対応させることが多く,grid[2][3]は「(0-originで)から2番目,から3番目」の要素と解釈することが多い気がするということです.
もちろんこれの転置をgridと置けばx, yという記号とスムーズに対応させることもできますが.

stack = [(x, y)]
visited.add((x, y))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好みの問題かとは思いますが,visitedへのaddはstackからのpop直後に行うのが好みです.
stackへの追加時点ではvisitedしたわけではなくvisit先の候補として追加しただけで,実際にvisitするのはpopして取り出した瞬間だと解釈しているからです.

area = 1
while stack:
x, y = stack.pop()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好みの範囲ですが、x, yよりrow, colの方が自分は好みです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x, y を2次元配列のどの順序で並べるかという面倒な議論があります。
https://en.wikipedia.org/wiki/Row-_and_column-major_order

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

パタヘネというコンピュータアーキテクチャの本を読んでおり、DGEMMのアセンブリ実装のあたりでちょうどそのトピックを見かけました!

行優先配置 (row major order): CやJavaで使用
列優先配置 (column major order): Fortranで使用

for dx, dy in directions:
next_x = x + dx
next_y = y + dy
if (next_x, next_y) in visited:
continue

if not (0 <= next_x < m and 0 <= next_y < n):
continue

if grid[next_x][next_y] == WATER:
continue

area += 1
Copy link
Copy Markdown

@naoto-iwase naoto-iwase Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pop後にインクリメントする方が自然な気がしますが、これも間違いではないですね。

stack.append((next_x, next_y))
visited.add((next_x, next_y))

return area

max_area = 0
for i in range(m):
for j in range(n):
if grid[i][j] == LAND:
if (i, j) in visited:
Comment on lines +43 to +44
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここはif grid[i][j] == LAND and (i, j) not in visited:にしてネストを下げたいですね。

continue

area = calculate_area_of_island(i, j)
max_area = max(max_area, area)

return max_area
```
前問と同様に再帰DFS, BFS, Union-Findで書き直してみる(復習)

1. 再帰DFS
ポイント
- visitedに追加するタイミング => visitedとスタックは基本的に同じタイミングで更新する => 非同期キューを考えると二度送信してはいけないとすると、キューへの追加とDBのステータス更新を同時にしないといけない
- areaをreturnすることで変数の破壊的変更を防ぐ
- area = calculate_area_of_island(i, j, 1)で面積の初期値は1にする
再帰DFSはスタックオーバーフローのリスクがある. 最悪深さO(M ✕ N)
```py
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0

m = len(grid)
n = len(grid[0])
LAND = 1
WATER = 0
visited = set()
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
def calculate_area_of_island(x, y, area):
visited.add((x, y))
for dx, dy in directions:
next_x = x + dx
next_y = y + dy
if (next_x, next_y) in visited:
continue

if not (0 <= next_x < m and 0 <= next_y < n):
continue

if grid[next_x][next_y] == WATER:
continue

area += 1
# visited.add((next_x, next_y)) ここでaddしても良い
area = calculate_area_of_island(next_x, next_y, area)

return area

max_area = 0
for i in range(m):
for j in range(n):
if grid[i][j] == LAND:
if (i, j) in visited:
continue

# visited.add((i, j))
area = calculate_area_of_island(i, j, 1)
max_area = max(max_area, area)

return max_area
```

2. BFS

```py
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0

m = len(grid)
n = len(grid[0])
LAND = 1
WATER = 0
visited = set()
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
def calculate_area_of_island(x, y, area):
queue = deque()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queueはPythonの標準モジュール名と被るので自分は避けるようにしています。代わりにfrontiersとかはどうでしょうか?

https://docs.python.org/3/library/queue.html

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私は結構 queue, stack を使ってしまうのですが、あまり行儀がよくないのはそうですね。

grid_to_visit, cells_to_explore などですかね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitですが,frontierと単数形にした方が英語的に適切だと思います(cf

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LeetCodeの実行環境では標準モジュール群がワイルドカードでimport済みなので問題にならないですが、本来はfrom collections import dequeが必要です。

queue.append((x, y))
visited.add((x, y))
while queue:
x, y = queue.popleft()
for dx, dy in directions:
next_x = x + dx
next_y = y + dy
if (next_x, next_y) in visited:
continue

if not (0 <= next_x < m and 0 <= next_y < n):
continue

if grid[next_x][next_y] == WATER:
continue

area += 1
visited.add((next_x, next_y))
queue.append((next_x, next_y))

return area

max_area = 0
for i in range(m):
for j in range(n):
if grid[i][j] == LAND:
if (i, j) in visited:
continue

area = calculate_area_of_island(i, j, 1)
max_area = max(max_area, area)

return max_area
```
3. Union-Find
時間, 空間計算量: O(M ✕ N). UnionとFindの計算量はアッカーマン関数の逆関数
ポイント
- findで経路圧縮を実装する

```py
class UnionFind:
def __init__(self, n):
self.parents = list(range(n))
self.area = [0] * n

# 経路圧縮なし: 計算量はO(logn)
# def find(self, x):
# if self.parents[x] == x:
# return x

# return self.find(self.parents[x])

# 経路圧縮あり: 計算量O(α(n))
def find(self, x):
if self.parents[x] != x:
self.parents[x] = self.find(self.parents[x])

return self.parents[x]

def union(self, x, y):
parent_x = self.find(x)
parent_y = self.find(y)
if parent_x == parent_y:
return

if self.area[parent_x] < self.area[parent_y]:
parent_x, parent_y = parent_y, parent_x

self.parents[parent_y] = parent_x
self.area[parent_x] += self.area[parent_y]

class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0

m = len(grid)
n = len(grid[0])
LAND = 1
WATER = 0
uf = UnionFind(m * n)
for i in range(m):
for j in range(n):
if grid[i][j] == LAND:
uf.area[i * n + j] = 1

for i in range(m):
for j in range(n):
if grid[i][j] == LAND:
idx = i * n + j
if j + 1 < n and grid[i][j + 1] == LAND:
uf.union(idx, idx + 1)

if i + 1 < m and grid[i + 1][j] == LAND:
uf.union(idx, idx + n)

return max(uf.area)
```
## Step2 他の人のコードやコメントなどを見る
https://github.com/colorbox/leetcode/pull/32/changes/BASE..9e158c529cc75864b1ecad429cecfbe15e0723a0#r1898178545
> stack に追加する前に範囲チェックをするのも一つです。問題によっては計算量が変わることもあります。そうすると、範囲チェックをして追加という、同じ処理が繰り返されるので関数化をしたりラムダにしたりするのがいいでしょう。

- visitedをsetではなく, m * nの配列にするのも良さそう. `visited[i][j] = True`のような使い方

- 再帰DFSの解法の別の書き方
```py
def calculate_area_of_island(x, y):
if not (0 <= x < m and 0 <= y < n):
return 0

if grid[x][y] == WATER:
return 0

if visited[x][y]:
return 0

visited[x][y] = True
area = 1 # (x, y)の面積分
for dx, dy in directions:
area += calculate_area_of_island(x + dx, y + dy)

return area
```

## Step3
Copy link
Copy Markdown

@naoto-iwase naoto-iwase Dec 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同じロジックでも色々書き方はあると思います。以下ご参考までに。

import copy
import collections


class Solution:
    def maxAreaOfIsland(self, grid: list[list[int]]) -> int:
        grid = copy.deepcopy(grid)
        num_rows = len(grid)
        num_cols = len(grid[0])
        WATER = 0
        LAND = 1

        def is_land(row, col):
            return (
                0 <= row < num_rows
                and 0 <= col < num_cols
                and grid[row][col] == LAND
            )
        
        def traverse(row_start, col_start):
            frontiers = collections.deque()
            
            def push_if_land(row, col):
                if is_land(row, col):
                    frontiers.append((row, col))
                    grid[row][col] = WATER
            
            push_if_land(row_start, col_start)
            area = 0
            while frontiers:
                row, col = frontiers.popleft()
                area += 1
                push_if_land(row + 1, col)
                push_if_land(row - 1, col)
                push_if_land(row, col + 1)
                push_if_land(row, col - 1)
            return area
        
        max_area = 0
        for row in range(num_rows):
            for col in range(num_cols):
                if is_land(row, col):
                    area = traverse(row, col)
                    max_area = max(max_area, area)
        return max_area   

```py
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
if not grid:
return 0

m = len(grid)
n = len(grid[0])
LAND = 1
WATER = 0
visited = set()
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
def calculate_area_of_island(x, y):
stack = [(x, y)]
visited.add((x, y))
area = 1
while stack:
x, y = stack.pop()
for dx, dy in directions:
next_x = x + dx
next_y = y + dy
if (next_x, next_y) in visited:
continue

if not (0 <= next_x < m and 0 <= next_y < n):
continue

if grid[next_x][next_y] == WATER:
continue

area += 1
stack.append((next_x, next_y))
visited.add((next_x, next_y))

return area

max_area = 0
for i in range(m):
for j in range(n):
if grid[i][j] == LAND:
if (i, j) in visited:
continue

area = calculate_area_of_island(i, j)
max_area = max(max_area, area)

return max_area
```