こんにちは
今回の学びは
・number of islandsはグラフ問題の代表作
・問題をグラフ問題に変換してdfsで解けそうという定番のあわせ技
問題は。。。
(時間配分は5分問題理解 10分検討 10分実装 20分振り返り の一文45分構成)
まずは入出力をしっかりおさえましょう。
この問題はかなり有名だとおもいます。
与えられた配列をグラフと見立てて、連続する
とある条件をdfsしていく、という問に変換します
visitted といS(n)をもうけて、O(n)で処理できるとおもいます
とある条件とはdfsが前のノードと同値 そのノードの値が1だったら
という条件でいかきます。さらにいくつ島が合ったかの合計を返すようにしようかな、とおもう。
i, jで配列をループして、dfsにかける。返り値が1以上なら島ということに
して最後にその合計をかぞえる。にしよう。
実装前後でこれは毎回書きましょう。timespace analysis (時間とスペースの分析)は O(n) , S(n+n)
実際の実装はこちら
class Solution:
def numIslands(self, S: List[List[str]]) -> int:
m = len(S)
if m == 0: return 0
n = len(S[0])
visit = [ [False] * n for _ in range(m) ]
# def neighbours(i, j):
# directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
# nbs = []
# for h, v in directions:
# if 0 <= i + v < n and 0 <= j + h < m:
# nbs.append((i+v, j+h))
# return nbs
def neighbours(i, j):
return [
(i + a, j + b)
for (a, b) in [(-1, 0), (1, 0), (0, -1), (0, 1)]
if 0 <= i + a < m
if 0 <= j + b < n
]
# dfsのポイント!親にもどらないようにしないと永遠に再帰する
def dfs_forever(i, j, sofar=0):
ttl = sofar
if S[i][j] == '1':
for v, h in neighbours(i, j):
if not visit[v][h]:
ttl += dfs(v, h, sofar+1)
#visit[i][j] == True # わぉ!、こんなミスが!!! (その後下のように変更修正) まぁ、それでも永遠再帰には変わりないw
visit[i][j] = True #
return ttl
# dfsのポイント!親にもどらないようにしないと永遠に再帰する
def dfs_still_forever(i, j, parent:tuple = None, sofar=0):
ttl = sofar
if S[i][j] == '1':
for v, h in neighbours(i, j):
if not visit[v][h]:
if parent == None or parent != (i, j):
ttl += dfs(v, h, (i, j), sofar+1)
visit[i][j] == True
return ttl
# dfsのポイント!そうじゃないんだ!visitフラッグのタイミングを冒頭にすればいいだけなんだ!
# しかし、ほぼ正解なんだが、[[1]]などのエッジケースでとりこぼすぞ!
def dfs_edge(i, j, sofar=0):
visit[i][j] = True
ttl = sofar
if S[i][j] == '1':
for v, h in neighbours(i, j):
if not visit[v][h]:
ttl += dfs(v, h, sofar+1)
return ttl
# うえのようなエッジケースをカバーした正解dfsがこちら
# ttlに最初に+1しているのが、変更点です。おわかりいただけただろうか。
def dfs(i, j, sofar=0):
visit[i][j] = True
ttl = sofar
if S[i][j] == '1':
ttl += 1
for v, h in neighbours(i, j):
if not visit[v][h]:
ttl += dfs(v, h, ttl)
return ttl
ans = 0
for i in range(m):
for j in range(n):
if not visit[i][j]:
if dfs(i, j) > 0:
ans += 1
return ans
僕の中で number of islandsはグラフ問題の代表作 です。
問題をグラフ問題に変換してdfsで解けそうという定番のあわせ技
既にvisit済の状態をもつべきか
さっき来た親を知っているべきか、しらなくてもvisit状態がわかればいいのか。
それを踏まえ、どのタイミングでvisit状態とするのか
など
慎重に考えを重ねるポイントがあります。
また返り値も何にするかなど
総合的に、典型的なグラフ問題で考えるべきポイントが自然にあらわれる
良問でした。
実装後です。timespace analysis (時間とスペースの分析)は
O(n)です
前にもでましたが、nighbours関数便利です。
pythonで競技プログラミングをする際のチップにも追記しました。
# def neighbours(i, j):
# directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
# nbs = []
# for h, v in directions:
# if 0 <= i + v < n and 0 <= j + h < m:
# nbs.append((i+v, j+h))
# return nbs
def neighbours(i, j):
return [
(i + a, j + b)
for (a, b) in [(-1, 0), (1, 0), (0, -1), (0, 1)]
if 0 <= i + a < m
if 0 <= j + b < n
]
まとめ
・number of islandsはグラフ問題の代表作
・問題をグラフ問題に変換してdfsで解けそうという定番のあわせ技
以上です