
こんにちは
今回の学びは
・ chosen[:]のコピーであることに注意!バックトラックではchosenを使いまわしているので、
こうする必要があることに注意。
・どこに戻すか?が重要で、以前と同じ場所でないと、いけない.
・バックトラッキングの方法は、再起を呼び出す前の状態に完全に戻すこと!が動作する条件
問題はpermutations
(時間配分は5分問題理解=restate&clarify 10分検討multip-sol, analysis, reasons 10分実装 20分振り返り の一文45分構成)をできればgoogle docで笑&真剣。
終始、わからないことは声にだして、明確にしていきましょう。
1,まずは入出力をしっかりおさえましょう。(問題を自分の言葉で復唱すること、わからないことをクリアにすると、前提も声にだすこと)
2,いくつかのソリューションを声にだして出してみましょう。(もちろんtime complexit, space complexityの分析や理由を付け加えて)
3,どれでいくか、一緒に決定しましょう。(理由付きでね、よろしく)
timespace analysis (時間とスペースの分析)は 〇〇です
O(2^N)
今回の入力条件はdistinctだったので、一個づつ選んでいくという作戦も効いた。
そうでなければ最初の回答のようにchosenを持ち歩く必要があると思う。
4、実装しましょう。
メモリをぶん回すBF実装
class Solution_ok:
def permute(self, nums: List[int]) -> List[List[int]]:
def rec(options, chosen):
ans = []
nums = options[:]
#print(options, chosen)
if len(options) == 0:
#print(chosen)
ans.append(chosen)
else:
for i in range(len(nums)):
options = nums[:]
chosen_copy = chosen[:]
chosen_copy.append(nums[i])
del options[i]
ans += rec(options, chosen_copy)
return ans
return rec(nums, [])
これで40ms
枝刈りでだいぶ改善されて28ms。これで上位5%だ。
パフォーマンスの差はコピーの回数と思う。コピー自体、O(N)かかっちゃうからね。
inspired by https://leetcode.com/problems/permutations/discuss/496859/Python-Backtracking-beats-88
返却値として、premutationsに追加している部分は chosen[:]のコピーであることに注意!バックトラックではchosenを使いまわしているので、 こうする必要があることに注意。
class Solution:
"""
1 2 3
[1] .. [1 3] .. [ 1 3 2 ]
"""
def permute(self, nums: List[int]) -> List[List[int]]:
def backtrack(nums, chosen, permutations):
if len(chosen) == len(nums):
# chosen[:]のコピーであることに注意!バックトラックではchosenを使いまわしているので、
# こうする必要があることに注意。
permutations.append(chosen[:])
else:
for i in range(len(nums)):
# 入力の特徴を活かす
# collection of distinct integers, return all possible permutations.
if nums[i] not in chosen:
chosen.append(nums[i])
backtrack(nums, chosen, permutations)
chosen.remove(nums[i])
permutation, permutations = [], []
backtrack(nums, permutation, permutations)
return permutations
バックトラッキングで、配列を配下にながして、もどってきたら元に戻す
という方法になれてきたので、最初の実装も、そのように修正してみた。
途中、options.append(value)とやっていたが
これには落とし穴があり、
・どこに戻すか?が重要で、以前と同じ場所でないと、いけない.
という重要なポイントいもきづけた。バックトラッキングの方法は、再起を呼び出す前の状態に完全に戻すこと!が動作する条件
であることがわかった
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def rec(options, chosen):
ans = []
if len(options) == 0:
ans.append(chosen[:])
else:
N = len(options)
for i in range(N):
value = options[i]
chosen.append(value)
options.remove(value)
ans += rec(options, chosen)
# 元に戻す
options.insert(i, value) # どこに戻すか?が重要で、以前と同じ場所でないと、いけない.
chosen.remove(value)
return ans
return rec(nums[:], [])
5、エッジケースとサンプルテストを忘れずに!
振り返り
「次のこの問題を解くばあい知っていればよい一つの事実とは?」
まとめ
・ chosen[:]のコピーであることに注意!バックトラックではchosenを使いまわしているので、
こうする必要があることに注意。
・どこに戻すか?が重要で、以前と同じ場所でないと、いけない.
・バックトラッキングの方法は、再起を呼び出す前の状態に完全に戻すこと!が動作する条件
以上です