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
169 changes: 169 additions & 0 deletions 46. Permutations/46. Permutations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# 46. Permutations
## STEP1
- 何も見ずに解いてみる
- 再帰的に書く。長さ1の場合は一通りのみ。そうでない場合は先頭に来る要素を順に選び、残りの要素を再帰的に並び替える。

```python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if len(nums) == 1:
return [nums]

results = []
for i in range(len(nums)):
sub_nums = [num for j, num in enumerate(nums) if j != i]
sub_permutes = self.permute(sub_nums)
for sub_permute in sub_permutes:
results.append([nums[i]] + sub_permute)
return results
```
- 改善点案1: 制約が小さいので実行時間には余裕がありそうだが、効率的にできる部分はありそう。nums の要素をswapし、並び替えるnumsのindexを渡す。
- results.append(sub_permute + [nums[start]]) とする必要があるが、出力の順番が自然ではなくなるので微妙。
- あとで見返すと sub_permute + [nums[start]] の部分は
sub_permute.append(nums[start]); results.append(sub_permute) でないと意味がなさそう。
```python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def permute_helper(start: int) -> List[List[int]]:
if start == len(nums) - 1:
return [[nums[start]]]

results = []
for i in range(start, len(nums)):
nums[start], nums[i] = nums[i], nums[start]
for sub_permute in permute_helper(start + 1):
results.append(sub_permute + [nums[start]])
nums[start], nums[i] = nums[i], nums[start]
return results

return permute_helper(0)
```

- 改善点案2: 出力される長さが大きい状況ではyieldする書き方もありそう。
```python
from collections.abc import Iterator


class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if len(nums) == 1:
return [nums]

def permute_helper() -> Iterator[list[int]]:
for i in range(len(nums)):
sub_nums = [num for j, num in enumerate(nums) if j != i]
for sub_permute in self.permute(sub_nums):
yield [nums[i]] + sub_permute

return list(permute_helper())
```
## STEP2
### プルリクやドキュメントを参照
- https://github.com/olsen-blue/Arai60/pull/51/files
- 色々書き方があって面白い。
- 作っている途中の list を引き継ぎつつ、使っていない数字を追加する。その数字を使った場合を列挙したらそれを取り除く。直感的でわかりやすい。以下書いてみた。
- 再帰で書いたがstackでも書ける。
- num in sub_permutation で使用済みの数字を判定した。O(n) かかる。全体で少なくともO(n!) かかるので、他の部分はさほど気にしなくて良い気もする。
- 似たような意見: https://github.com/fhiyo/leetcode/pull/50/files#diff-76c4644fcedcaf9f8c6b6283fa29fa6b79699ef29285914474e37e05a3dbe5f7R43
- 結局は与えられる入力と、許容される実行時間による。
- 実行時間は間に合っても $O(n \times n \times n!)$ になっていたりすると、読み手には違和感を覚えさせるかも。
```python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
permutations = []
def permute_helper(sub_permutation: list[int]) -> None:
if len(sub_permutation) == len(nums):
permutations.append(sub_permutation[:])
return
for num in nums:
if num in sub_permutation:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

使用中の num を set に入れておくと、時間計算量は下がりそうです。ただ、 1 <= nums.length <= 6 のため、こちらのほうが速いかもしれません。

continue
sub_permutation.append(num)
permute_helper(sub_permutation)
sub_permutation.pop()

permute_helper([])
return permutations
```
- https://docs.python.org/3/library/itertools.html#itertools.permutations
- Roughly equivalent とされている下記コードを読み解いてコメントを書いてみた。
- 結構複雑ですね。
```python
def permutations(iterable, r=None):
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return

indices = list(range(n)) # 先頭r個が出力対象になるように入れ替えていくindex
cycles = list(range(n, n-r, -1)) # 先頭r個の要素について、後ろから何番目の要素と交換すべきかを管理する
yield tuple(pool[i] for i in indices[:r])

while n:
for i in reversed(range(r)): # 辞書順で出るように index が大きい方から探索
cycles[i] -= 1
if cycles[i] == 0:
# i番目の探索のために swap した状態を元に戻す
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
# break されず i - 1 番目の探索に移行
else:
j = cycles[i] # 交換対象の index を取得
indices[i], indices[-j] = indices[-j], indices[i] # swap
yield tuple(pool[i] for i in indices[:r])
break
else:
return
Comment on lines +104 to +117
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私はこの部分が特に気に食わなくて、

  • if else を入れ替えます。
  • else のインデントを下げます。
  • break を先に書いて、yield を for の外に追い出します。

とすると思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

私も読んでいる時 if else の部分は微妙だと思っていました。
yield を for の外に追い出すのは思いつきませんでした。while の役割が明確になってわかりやすくなると感じます。

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        indices = list(range(n))
        cycles = list(range(n, 0, -1))
        results = []
        results.append([nums[i] for i in indices])
        while True:
            for i in reversed(range(n)):
                cycles[i] -= 1
                if cycles[i] > 0:
                    j = cycles[i]
                    indices[i], indices[-j] = indices[-j], indices[i]
                    break
                indices[i:] = indices[i + 1 :] + indices[i : i + 1]
                cycles[i] = n - i
            else:
                break
            results.append([nums[i] for i in indices])
        return results

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2つの append をまとめてループの先頭に回すこともできますね。

                    j = cycles[i]
                    indices[i], indices[-j] = indices[-j], indices[i]

これも for の外に追い出せます。(どちらがいいかは難しいところ。)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

そうですね。do-while のような処理をまとめる形で書くのが苦手だなと感じています。
for の仕事として適切な i を見つけるところで終わるのか、indices の変更までしてもらうのかというところですね。
cycles[i] == 0 の時 for 内で indices を整えている点、cycles[i] > 0 で break してから for の処理がやや複雑な点(else:break がある)、を考えると for 内に書くのが個人的には自然に思います。

```
- https://cpprefjp.github.io/reference/algorithm/next_permutation.html
- こちら実装してみる。
```python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
indices = list(range(len(nums)))
results = [[nums[i] for i in indices]]
while self.next_permutation(indices):
results.append([nums[i] for i in indices])
return results

def next_permutation(self, arr: list[int]) -> bool:
for i in reversed(range(len(arr) - 1)):
if arr[i] < arr[i + 1]:
j = len(arr) - 1
while arr[j] < arr[i]: # at least j = i + 1 break this loop
j -= 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1 :] = reversed(arr[i + 1 :])
return True
return False



```

## STEP3
### 3回ミスなく書く
```python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
permutations = []

def permutation_helper(sub_permutation):
if len(sub_permutation) == len(nums):
permutations.append(sub_permutation[:])
return
for num in nums:
if num in sub_permutation:
continue
sub_permutation.append(num)
permutation_helper(sub_permutation)
sub_permutation.pop()

permutation_helper([])
return permutations
```

3分,2分,2分で3回Accept

itertools.permutations と next_permutation も再現してみたが、itertools.permutations はとても書きにくい。自然な理解ができていないのだと思う。来週には書けなくなっていそう。