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
92 changes: 92 additions & 0 deletions Python3/46. Permutations.md
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で対応

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のときは、参照が共通でもどうせ変更があれば参照ごと書き換わる(再代入される)ので困らないという理屈です。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。初めはdeepcopyにしていたんですが、shallow copyでも行けることに気が付いたので修正した次第です。コメントは直し忘れていました


```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!)

Choose a reason for hiding this comment

The 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とも呼ぶらしい?がほぼ同じに見える

Choose a reason for hiding this comment

The 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:])
Copy link

Choose a reason for hiding this comment

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

個人的には + の両側にスペースを空けたいです。

参考までに、関連するスタイルガイドを共有いたします。

https://peps.python.org/pep-0008/#other-recommendations

Always surround these binary operators with a single space on either side: assignment (=), augmented assignment (+=, -= etc.), comparisons (==, <, >, !=, <=, >=, in, not in, is, is not), Booleans (and, or, not).

https://google.github.io/styleguide/pyguide.html#36-whitespace

Surround binary operators with a single space on either side for assignment (=), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), and Booleans (and, or, not). Use your better judgment for the insertion of spaces around arithmetic operators (+, -, *, /, //, %, **, @).

なお、これらのスタイルガイドは唯一絶対のルールではなく、数あるガイドラインの一つに過ぎません。チームによって重視する書き方が異なる場合もあります。
そのため、ご自身の中に基準を持ちつつも、最終的にはチーム内で一般的とされる書き方に寄せていくことをお勧めいたします。

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

Choose a reason for hiding this comment

The 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)にできそうです。これは同じく定数倍最適化ですが、結構効きそうな感覚があります。

Copy link
Owner Author

Choose a reason for hiding this comment

The 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]
Copy link

Choose a reason for hiding this comment

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

これ、[permuted_nums] をここに持ってくるのは意味がだいぶ違うので個人的にはあまり好きではないですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに意味としては直感的ではないですね。少し迷って短く書いてみたんですが微妙でした

return permute_helper([])
```