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
153 changes: 153 additions & 0 deletions 695. Max Area of Island/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@

## step1:
まずどこかlandに着いたら深さ優先探索でその島を探索し尽くそうと思い、dfs関数で上下左右で未探索かつlandである場所を探索していった。ここで、どうやって各島の面積を保存しようと思い悩み、関数に数値を保存するオブジェクトを渡してlandを見つけるたびに1づつ足していけばいいと考えた。そこで各島にラベルを付け、島を見つけるたびに各ラベルに1づつ足していき最後にそれらのmax値を返却した。

### code
```python
from collections import defaultdict
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
visited = [[0]*n for _ in range(m)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

こちらのコメントをご参照ください。
mt2324/leetcode#2 (comment)

max_area = 0
area_dict = defaultdict(int)
label_max = 0

def dfs(p, q, dict, label):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

関数名は動詞の原型または命令形から始まることが多いように思います。また、 dfs という単語は、関数の実装内容の詳細に示し過ぎているように思います。 traverse はいかがでしょうか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

claudeに聞いてみると

traverse — グラフ・木を「巡る/たどる」という移動プロセスに焦点。汎用的で広く使われる。
explore — 「未知の領域を探索する」というニュアンスで、島問題との相性が少し強い。
traverse が特に自然に感じられる文脈は、戻り値を使わずに副作用(カウンタの更新など)で面積を記録するスタイルのときです。
面積を返すスタイルなら explore や countCells、副作用で記録するスタイルなら traverse が収まりよく感じます。

と来たので今回は副作用をもたらす関数として使ってるのでtraverseが良さそうですね。

nonlocal visited
visited[p][q] = 1
area_dict[label] += 1
if p-1 >= 0 and visited[p-1][q] == 0 and grid[p-1][q] == 1:
dfs(p-1, q, area_dict, label)
if p+1 < m and visited[p+1][q] == 0 and grid[p+1][q] == 1:
dfs(p+1, q, area_dict, label)
if q-1 >= 0 and visited[p][q-1] == 0 and grid[p][q-1] == 1:
dfs(p, q-1, area_dict, label)
if q+1 < n and visited[p][q+1] == 0 and grid[p][q+1] == 1:
dfs(p, q+1, area_dict, label)

for i in range(m):
for j in range(n):
if visited[i][j] == 0 and grid[i][j] == 1:
dfs(i, j, area_dict, label_max)
label_max += 1
visited[i][j] = 1
if len(area_dict.values()) == 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.

not area_dictで良いと思います。

return 0
max_area = max(area_dict.values())
return max_area
```

## step2:
最後のmax_areaを求めるところを、各島の面積を求めたところで判別できると感じたので削除した。

### code
```python
from collections import defaultdict
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
visited = [[0]*n for _ in range(m)]
max_area = 0
area_dict = defaultdict(int)
label_max = 0

def dfs(p, q, dict, label):
nonlocal visited
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Step 3では消えていますが、不要ですね。

visited[p][q] = 1
area_dict[label] += 1
if p-1 >= 0 and visited[p-1][q] == 0 and grid[p-1][q] == 1:
dfs(p-1, q, area_dict, label)
if p+1 < m and visited[p+1][q] == 0 and grid[p+1][q] == 1:
dfs(p+1, q, area_dict, label)
if q-1 >= 0 and visited[p][q-1] == 0 and grid[p][q-1] == 1:
dfs(p, q-1, area_dict, label)
if q+1 < n and visited[p][q+1] == 0 and grid[p][q+1] == 1:
dfs(p, q+1, area_dict, label)

for i in range(m):
for j in range(n):
if visited[i][j] == 0 and grid[i][j] == 1:
dfs(i, j, area_dict, label_max)
max_area = max(max_area, area_dict[label_max])
label_max += 1
visited[i][j] = 1
return max_area
```


## step3:
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を使わない、iterative DFS/BFS、union findなど色々あるかと。


### code
```python
from collections import defaultdict
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
visited = [[0]*n for _ in range(m)]
max_area = 0
area_dict = defaultdict(int)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

全部の島の面積を覚えるのはオーバーキルかと思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

確かにlabelをいちいちインクリメントして格納しておく必要はなかったですね...

max_label = 0

def dfs(p, q, dict, label):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

built-in typesをシャドーしない方がいいと思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

p, qをmatrixのインデックスとして使うのは珍しい気がします。i, jやrow, col、y, xが一般的だと思います。

visited[p][q] = 1
dict[label] += 1
if p-1 >= 0 and visited[p-1][q] == 0 and grid[p-1][q] == 1:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

下で言及ありますが、やや繰り返しがすぎるように感じました。8方向に繋がっている場合とかだと、どうするかを考えると良いかもしれません。

dfs(p-1, q, dict, label)
if p+1 < m and visited[p+1][q] == 0 and grid[p+1][q] == 1:
dfs(p+1, q, dict, label)
if q-1 >= 0 and visited[p][q-1] == 0 and grid[p][q-1] == 1:
dfs(p, q-1, dict, label)
if q+1 < n and visited[p][q+1] == 0 and grid[p][q+1] == 1:
dfs(p, q+1, dict, label)

for i in range(m):
for j in range(n):
if visited[i][j] == 0 and grid[i][j] == 1:
dfs(i, j, area_dict, max_label)
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_dictは引数で渡していますが、visitedはenclosing scopeからアクセスするのは一貫性がないように感じました。

max_area = max(max_area, area_dict[max_label])
max_label += 1
visited[i][j] = 1
return max_area
```

## 他の方のコードを読んだ所感
https://github.com/hemispherium/LeetCode_Arai60/pull/18/changes
遷移先でgridの外に出てしまうか判断しているので、自分のコードのように何度もif文を書く必要がなくバグが出にくい。cppはintがimmutableでないのでそのままdfsの関数の引数に入れてインクリメントして大丈夫そうである。自分は今回visitedという配列を用いて訪問済みか判断していたが、この方はgridを0にしていて訪問済みのマークと同等の効果を出していて効果的だと感じた。

https://github.com/Manato110/LeetCode-arai60/pull/18/changes
この方はnum_rows, num_colsと名付けていたが問題文でm×nと定義されているのでm, nで定義した方が簡潔な気がする。あと関数をクラスの中の1つの関数としていたので、関数の中での関数として定義していた自分よりもテストがしやすい。
```python
if not (0 <= row < num_rows and 0 <= col < num_cols):
```
この条件式の書き方は参考にできる。0, 1は条件文でWATER, LANDとなっているが、この方はクラス内で改めてWATER = 0, LAND = 1と書いて条件式でself.WATERと書いているので非常に可読性が良い。visited判定もset()に訪れた座標を入れていて、setは確か内部的にハッシュテーブルを利用しているはずなのでO(1)でアクセスできる。よって自分の書いたvisited配列と同じメモリ計算量、時間計算量で利用できる。

## iterative dfsで解いてみた
```python
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
max_area = 0
m, n = len(grid), len(grid[0])

def get_area_starting_from(i, j):
stack = []
area = 0
stack.append((i, j))
while stack:
y, x = stack.pop()
if not (0 <= y < m and 0 <= x < n and grid[y][x]):
continue
grid[y][x] = 0
area += 1
for col, row in [(y-1, x), (y+1, x), (y, x-1), (y, x+1)]:
stack.append((col, row))
return area

for i in range(m):
for j in range(n):
if grid[i][j] == 1:
max_area = max(max_area, get_area_starting_from(i, j))

return max_area
```