-
Notifications
You must be signed in to change notification settings - Fork 0
200. Number of Islands #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
``` | ||
|
||
## 他の人のPRやコメントを踏まえて実装 | ||
leetcodeの解法を見ると、dfs, bfs, union findの3つあった。union findは名前しか知らなかったので調査する。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
いわゆる競プロの問題集(e.g., Algorithms for Competitive Programming)でUnion Findに何回か遭遇した記憶がありますが、一般常識ではないような気がする(?)ので、インタビュー時は素直にBFSかDFSを実装するのが無難かもしれませんね。レビュワーが一回調べる手間が入るかも 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
個人的な感覚、union find は現実的には多くの人が知っているし、知らなくても書かれれば読めるんですが、しかし、知らない同僚がいたとしても別に動揺しないというラインだと思いますね。
一方で、書いてきたらきれいに書かれているかは気にするでしょうね。クラスの設計が評価に入るので、難易度は高めのコースを自分で選んだようには見えるでしょう。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、UnionFindは必須ではないレベルの知識なんですね
n = len(grid[0]) | ||
visited = [[False for _ in range(n)] for _ in range(m)] | ||
|
||
def visit_adjacent_lands(grid: List[List[str]], i: int, j: int) -> List[List[str]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ごく個人的に、私だったら返り値はNone
にしますね。その方が、この関数は副作用がある (visited
を書き換えている)とシグネチャを見るだけでわかりやすいので。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
今回はgridを変更していないので混乱させてしまいますね、ありがとうございます!
def visit_adjacent_lands(grid: List[List[str]], i: int, j: int) -> List[List[str]]: | ||
visited[i][j] = True | ||
|
||
if 0 <= i - 1 and grid[i - 1][j] == '1' and not visited[i - 1][j]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かに、先に範囲・visitedチェックをしておくとstackの使用が多少省けるのですが、私は後でチェックする方が読みやすく感じます。今回の場合、大きくパフォーマンスにも影響しないと思いますし、可読性を優先しても良いかな、と。
def visit_adjacent_lands(row: int, col: int) -> None:
if not (0 <= row < num_rows) or not (0 <= col < num_cols):
return
if visited[row][col]:
return
if grid[row][col] == "0":
return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
書いてみたのですが、たしかに可読性高いですね。
条件式を4回書いてたのがなくなってスッキリしました
visit_adjacent_landsの返り値はboolにしてみました
Noneだと、for文の中でvisit_adjacent_landsの呼び出しとnum_islandsのインクリメントにif文が必要になって条件式が重複するためです
visit_adjacent_landsの返り値がNoneの場合のコード
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
num_rows = len(grid)
num_cols = len(grid[0])
visited = [[False] * num_cols for _ in range(num_rows)]
WATER = '0'
LAND = '1'
num_islands = 0
def visit_adjacent_lands(row: int, col: int) -> None:
if not (0 <= row < num_rows and 0 <= col < num_cols):
return
if grid[row][col] == WATER:
return
if visited[row][col]:
return
visited[row][col] = True
visit_adjacent_lands(row - 1, col)
visit_adjacent_lands(row, col - 1)
visit_adjacent_lands(row + 1, col)
visit_adjacent_lands(row, col + 1)
for row in range(num_rows):
for col in range(num_cols):
if grid[row][col] == LAND and not visited[row][col]:
visit_adjacent_lands(row, col)
num_islands += 1
return num_islands
visit_adjacent_landsの返り値がboolの場合のコード
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
num_rows = len(grid)
num_cols = len(grid[0])
visited = [[False] * num_cols for _ in range(num_rows)]
WATER = '0'
LAND = '1'
num_islands = 0
def visit_adjacent_lands(row: int, col: int) -> bool:
if not (0 <= row < num_rows and 0 <= col < num_cols):
return False
if grid[row][col] == WATER:
return False
if visited[row][col]:
return False
visited[row][col] = True
visit_adjacent_lands(row - 1, col)
visit_adjacent_lands(row, col - 1)
visit_adjacent_lands(row + 1, col)
visit_adjacent_lands(row, col + 1)
return True
for row in range(num_rows):
for col in range(num_cols):
if visit_adjacent_lands(row, col):
num_islands += 1
return num_islands
```python | ||
class Solution: | ||
def numIslands(self, grid: List[List[str]]) -> int: | ||
m = len(grid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m, nが matrixのrow, columnの長さを表す、ってどれくらい一般的なのでしょうかね。私はnum_rows
, num_cols
の方がわかりやすく感じますが...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
num_rows
, num_cols
のほうが分かりやすいですね、ありがとうございます
自分の感覚だとよくある気がしていましたが、実務だとあまり見たことがないと思いました
grid = visit_adjacent_lands(grid, i, j + 1) | ||
return grid | ||
|
||
numberOfislands = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LeetCodeに合わせているものだと思いますが、一応、Pythonでの変数名はsnake_caseの方が一般的かなと思います。
https://peps.python.org/pep-0008/#function-and-variable-names
def numIslands(self, grid: List[List[str]]) -> int: | ||
m = len(grid) | ||
n = len(grid[0]) | ||
visited = [[False for _ in range(n)] for _ in range(m)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
私は [[False] * n for _ in range(m)]
と書きますが、どっちもどっちな気がします
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
外側のループが内包表記なのが気になったのですが、下記のような理由があったのですね
https://docs.python.org/3/faq/programming.html#how-do-i-create-a-multidimensional-list
勉強になりました!
|
||
# step2: 30分 | ||
## 自分でコード読みやすく整えて実装 | ||
メソッド名をdfsからvisit_adjacent_landsに修正した |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
良いと思います 👍 dfs()
だと、なんの操作をするのかわかりづらいですよね、ユーザからしたら、方法はなんでも良いから何をするのか教えてくれ、となりそうな気がします。
LAND = '1' | ||
|
||
class UnionFind: | ||
def __init__(self, grid: List[List[str]]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ちなみに、最近のPythonのバージョンではlist[list[str]]
の方が推奨のようですね
https://docs.python.org/3/library/typing.html#typing.List
空間計算量:O(m * n) | ||
|
||
## 実装 | ||
最初 `dfs`の中で島かどうか判定する処理を抜かしてしまっていた |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これ、私も同じことをして最初のDFSでvisited
が全てTrueになりました 😂
|
||
numberOfislands = 0 | ||
for i in range(m): | ||
for j in range(n): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i
, j
が何を表しているのかわかりづらいので、row_i
, col_i
などとするのはいかがでしょうか。
確かThe Art of Readable Code内で、わかりやすいloop iteratorsの命名について言及されていたような気がするので、確認してみていただくのも良いかもしれません。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
特に複数のループがある場合はちゃんと分かる名前にした方が良さそうですね
リーダブルコードも確認してみます
n = len(grid[0]) | ||
visited = [[False for _ in range(n)] for _ in range(m)] | ||
|
||
def dfs(grid: List[List[str]], i: int, j: int) -> List[List[str]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
あくまで個人的な好みですが、縦横の座標を表す変数名は (x, y)、(row, column)、(r, c) などを使います。 (i, j) は行列の添え字には使いますが、縦横の座標を表す際にはあまり使わないです。
|
||
for i in range(m): | ||
for j in range(n): | ||
if grid[i][j] == '1': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if grid[i][j] == '0':
continue
としたほうが、ネストが浅くなり、読みやすくなると思います。
unionの処理が間違っていた。 | ||
```python | ||
class UnionFind: | ||
def __init__(self, grid: List[List[str]]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UnionFind のクラスが、外側の LeetCode の問題の内容を知らなければならないという設計に和漢を感じました。自分なら UnionFind の要素のサイズ (m * n) のみコンストラクターで渡すと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たしかに、コンストラクタの内部ロジックもgridに依存しているので分かりづらいですね
UnionFind自体はこの問題とは独立して実装しておくべきでした(その方が再利用もできる)
def __init__(self, grid: List[List[str]]): | ||
m = len(grid) | ||
n = len(grid[0]) | ||
self.parent = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
list で実装したほうが処理が軽くなると思いました。
visited[i][j] = True | ||
|
||
if 0 <= i - 1 and grid[i - 1][j] == '1' and not visited[i - 1][j]: | ||
grid = dfs(grid, i - 1, j) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここを読むと、grid が更新されるのかなと思いました。更新しない、引数に入れなくても grid にはアクセスできるので、引数には grid は不要と思いました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たしかに、numIslands
内でdfs
を定義しているので不要ですね
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
下書きで置いておいたら他の方のレビューも入ってました。内容は重複しているものがほとんどのようです。一応レビューしましたということで submit しておきます。全体的に変数名 (関数も含む) の付け方に改善の余地があると思います。まだであればこのあたりもご覧になるとよいかもしれません。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.fcs3httrll4l
m = len(grid) | ||
n = len(grid[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
個人的にはm, nどっちがどっちがわからなくなるので rows, cols とかの方が好きです。
def numIslands(self, grid: List[List[str]]) -> int: | ||
m = len(grid) | ||
n = len(grid[0]) | ||
visited = [[False for _ in range(n)] for _ in range(m)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
visited = [[False] * rows for _ in range(cols)]
とも書けますね。好みだと思います。
n = len(grid[0]) | ||
visited = [[False for _ in range(n)] for _ in range(m)] | ||
|
||
def dfs(grid: List[List[str]], i: int, j: int) -> List[List[str]]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
個人的に、dfs という関数名はあまり意味がないので好きではないです。traverse_island
とかもうちょっと意味のあるものにしたい感覚があります。
ありがとうございます! |
問題:200. Number of Islands200. Number of Islands
次の問題:695. Max Area of Island695. Max Area of Island