-
Notifications
You must be signed in to change notification settings - Fork 0
Solved Arai60/46. Permutations #49
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| ## Step 1. Initial Solution | ||
|
|
||
| - 一つずつ残っているリストから数字を取り出して末尾に入れる | ||
| - 作成済みの順列と残りのリストをペアで保持して処理していく | ||
| - なぜかエラー→next_remaining_nums=remaining_numsにすると参照先をremoveしてしまう | ||
| - copy.deepcopyで対応 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def permute(self, nums: List[int]) -> List[List[int]]: | ||
| result = [] | ||
| prefix_to_remaining = [([], nums)] | ||
| while prefix_to_remaining: | ||
| prefix, remaining_nums = prefix_to_remaining.pop() | ||
| for num in remaining_nums: | ||
| next_prefix = prefix + [num] | ||
| next_remaining_nums = remaining_nums.copy() | ||
| next_remaining_nums.remove(num) | ||
| if not next_remaining_nums: | ||
| result.append(next_prefix) | ||
| else: | ||
| prefix_to_remaining.append((next_prefix, next_remaining_nums)) | ||
| return result | ||
| ``` | ||
|
|
||
| ### Complexity Analysis | ||
|
|
||
| - 時間計算量:O(N*N!) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 出力サイズ自体がN!個で各要素が長さNなので、時間計算量もどうしてもO(N * N!)は越えられなさそうですが、このオーダーでいかに定数倍最適化できるかが本問の工夫のポイントになってくると思います。 |
||
| - 空間計算量:O(N*N!) | ||
|
|
||
| ## Step 2. Alternatives | ||
|
|
||
| - 再帰関数で先頭とそれ以外の並び替えを回す方法が多い | ||
| - https://github.com/tokuhirat/LeetCode/pull/50/files#diff-f980f45b4d4f94c41556977f060a371872f422650416425386f1322e52deba58R2 | ||
| - ここは綺麗に書けていて分かりやすいと感じた | ||
| - https://github.com/tokuhirat/LeetCode/pull/50/files#diff-f980f45b4d4f94c41556977f060a371872f422650416425386f1322e52deba58R47-R58 | ||
| - まだ入っていないものを追加していく方法 | ||
| - https://github.com/tokuhirat/LeetCode/pull/50/files#r2278840164 | ||
| - 時間計算量の話は1≤num.lengths≤6では線形の方が速そうというところ | ||
| - https://github.com/docto-rin/leetcode/pull/51/files#diff-47cd87d2eeba6169d55b484ea322c16f1a03278b0d29a5b0d00f771aaf816f8bR74-R78 | ||
| - Backtrackingとも呼ぶらしい?がほぼ同じに見える | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefix変数として持っているリストオブジェクトを、下のコードでは毎回再構築されていますが、私がbacktrackingと呼んで書いているものは1つのオブジェクトを使い回しているように書かれているはずです。 |
||
| - 再帰で書いてみる | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def permute(self, nums: List[int]) -> List[List[int]]: | ||
| if len(nums) == 1: | ||
| return [nums] | ||
|
|
||
| permutations = [] | ||
| for i in range(len(nums)): | ||
| prefix = [nums[i]] | ||
| sub_permutations = self.permute(nums[:i]+nums[i+1:]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 個人的には 参考までに、関連するスタイルガイドを共有いたします。 https://peps.python.org/pep-0008/#other-recommendations
https://google.github.io/styleguide/pyguide.html#36-whitespace
なお、これらのスタイルガイドは唯一絶対のルールではなく、数あるガイドラインの一つに過ぎません。チームによって重視する書き方が異なる場合もあります。 |
||
| for sub_permutation in sub_permutations: | ||
| permutations.append(prefix+sub_permutation) | ||
| return permutations | ||
| ``` | ||
|
|
||
| - 既出を管理する方法の方がスライスしない分速いか | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def permute(self, nums: List[int]) -> List[List[int]]: | ||
| def permute_helper(prefix: List[int]) -> List[List[int]]: | ||
| if len(prefix) == len(nums): | ||
| return [prefix] | ||
| permutations = [] | ||
| for num in nums: | ||
| if num in prefix: | ||
| continue | ||
| permutations += permute_helper(prefix + [num]) | ||
|
Comment on lines
+69
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. O(n)のスライスは回避していますが、if num in prefix:の判定でprefixはリストなのでO(n)、またprefix + [num]でリストの再構築がO(n)で、結局定数倍最適化の範疇ではありますね。 スライスはネイティブコードで走る部分なので、その定数倍も劇的ではなさそうだと推定します。 関連して、if num in prefix:ですが、bool配列やsetで使用済みを管理するオブジェクトを別に作っておくとO(1)にできそうです。これは同じく定数倍最適化ですが、結構効きそうな感覚があります。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そうですね。スライスをくっつけると2つコピーを作ってから更に新しく作ることになると思うのでそこが忌避感を持っていました。 num in prefixは確かにそうなんですが、prefixが最大でも6なのでハッシュはあまり意味がないだろうという判断です。bool配列はありですね。 |
||
| return permutations | ||
|
|
||
| return permute_helper([]) | ||
| ``` | ||
|
|
||
| ## Step 3. Final Solution | ||
|
|
||
| - ヘルパー関数を使う方法に帰着 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def permute(self, nums: List[int]) -> List[List[int]]: | ||
| def permute_helper(permuted_nums: List[int]) -> List[List[int]]: | ||
| permutations = [] | ||
| for num in nums: | ||
| if num in permuted_nums: | ||
| continue | ||
| permutations += permute_helper(permuted_nums + [num]) | ||
| return permutations if permutations else [permuted_nums] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これ、[permuted_nums] をここに持ってくるのは意味がだいぶ違うので個人的にはあまり好きではないですね。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 確かに意味としては直感的ではないですね。少し迷って短く書いてみたんですが微妙でした |
||
| return permute_helper([]) | ||
| ``` | ||
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.
deepcopyを使ってないように見えました。
使われているlist.copy()はshallow copyです。要素がmutableのときは動作に注意したいですね。今回のように要素がimmutableのときは、参照が共通でもどうせ変更があれば参照ごと書き換わる(再代入される)ので困らないという理屈です。
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.
ありがとうございます。初めはdeepcopyにしていたんですが、shallow copyでも行けることに気が付いたので修正した次第です。コメントは直し忘れていました