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
240 changes: 240 additions & 0 deletions 46_permutations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# 46. Permutations

## 1st

### ①

[cpythonのpermutationの内容](https://github.com/python/cpython/blob/bc264eac3ad14dab748e33b3d714c2674872791f/Modules/itertoolsmodule.c#L2603)を思い出しながら書いた。ギリギリ何とか書けたという感じ。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これ思い出しながら自力で書けるのすごいですね

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://docs.python.org/3/library/itertools.html#itertools.permutations
ここにもありますね。
Generator など、やはり言語の珍し目の機能を使うの楽しいですね。

所要時間: 23:52

n: len(nums)
- 時間計算量: O(n*n!)
- 空間計算量: O(n!) (出力でn!個返すため)

```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
return list(self._perms(nums))

def _perms(self, nums: list[int]) -> Iterator[list[int]]:
length = len(nums)
indices = list(range(length))
yield [nums[i] for i in indices]
cycles = list(range(length, 0, -1))
while length:
for i in reversed(range(length)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = length - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield [nums[i] for i in indices]
break
else:
return
```

### ②

上の解法はnPrの各要素を計算をするための方法。r==nでいいならもっと簡単に書ける。
`if i in indices` の部分は、どうせnが小さいので辞書にしてO(1)にしてもオーバーヘッド分で相殺されるかなと思い最適化はしていない。

`if len(indices) == len(nums)` の終わりはreturnすればよかった。

所要時間: 8:11

n: len(nums)
- 時間計算量: O(n^2 * n!)
- 空間計算量: O(n!)

```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def perm(indices: list[int]) -> Iterator[list[int]]:
if len(indices) == len(nums):
yield [nums[i] for i in indices]
for i in range(len(nums)):
if i in indices:
continue
indices.append(i)
yield from perm(indices)
indices.pop()

return list(perm([]))
```

### ③

1. 後ろから初めて昇順になっているindexを探す
2. 後ろから、1で見つけたindexの要素より初めて大きくなる要素のindexを探す
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これ降順になってるからmin_valueとか保存せずに大きくなった時点でbreakすればいいんですね。
自分ややこしいことやってました(勉強になりました)

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.

そうですね、自分はdiscord内で紹介されてたC++のドキュメント (https://en.cppreference.com/w/cpp/algorithm/next_permutation) を見て覚えました

3. 上で見つけた2つのindexの位置を交換する
4. 1のindexより右側を逆順にする (アルゴリズム上、必ず降順になっているので逆にしたら辞書順最小になる)

所要時間: 17:48

n: len(nums)
- 時間計算量: O(n*n!)
- 空間計算量: O(n!)

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

def perm(indices: list[int]) -> Iterator[list[int]]:
i = len(nums) - 2
while i >= 0 and indices[i] > indices[i + 1]:
i -= 1
if i == -1:
return
j = len(indices) - 1
while indices[i] > indices[j]:
j -= 1
indices[i], indices[j] = indices[j], indices[i]
indices[i+1:] = reversed(indices[i+1:])
yield [nums[i] for i in indices]
yield from perm(indices)

return [nums[:]] + list(perm(list(range(len(nums)))))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

list(perm(list(range(len(nums)))))はかっこが多くて追うのにちょっと時間かかりました

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.

たしかにこれは多いですね。

indices = list(range(nums))
return [nums[:]] + list(perm(indices))

なら良さそうですかね?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

そういえば、
#17 (comment)

心理学やマーケティングだと、マジカルナンバーといって、短期記憶の限界は7つか4つかという
みたいな話を前にしましたね。

```

## 2nd

### 参考

- https://discord.com/channels/1084280443945353267/1226508154833993788/1255879916113756202
- https://github.com/nittoco/leetcode/pull/21


[e_1, e_2, ..., e_k] のリストがあるとき、要素の順番を保存したままe_{k+1}を挿入すると
- [e_1, e_2, ..., e_k, e_{k+1}]
- [e_1, e_2, ..., e_{k+1}, e_k]
- ...
- [e_{k+1}, e_1, e_2, ..., e_k]

のk+1通りのリストができる。これをすべての要素の順番について列挙すれば、[e_1, ..., e_{k+1}]の要素を使ったすべての順列を書き出すことができる。

```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def perm(index: int) -> Iterator[list[int]]:
if index == len(nums):
yield []
return
for permutation in perm(index + 1):
permutation.append(nums[index])
yield permutation[:]
for i in reversed(range(len(permutation) - 1)):
permutation[i], permutation[i+1] = permutation[i+1], permutation[i]
yield permutation[:]

return list(perm(0))
```

ループでall_permutationsを更新していくやり方。変数名に自信はない。

```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
all_permutations = [[]]
for num in reversed(nums):
derived_permutations = []
for permutation in all_permutations:
permutation.append(num)
copied = permutation[:]
for i in reversed(range(len(copied) - 1)):
copied[i], copied[i+1] = copied[i+1], copied[i]
derived_permutations.append(copied[:])
all_permutations += derived_permutations
return all_permutations
```

- https://discord.com/channels/1084280443945353267/1233295449985650688/1245243664259878932
- https://github.com/Exzrgs/LeetCode/pull/19
- https://discord.com/channels/1084280443945353267/1196472827457589338/1241302279303204915
- https://github.com/Mike0121/LeetCode/pull/14


順列の置換を列挙すれば良い。個人的には一番分かりやすい気がする。

スライスによるindicesのコピーはしておくとスレッドセーフになる (はず)。マルチスレッドにするならコードも変えないといけないが...

```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if not nums:
return [[]]

def perm(indices: list[int], i: int) -> Iterator[list[int]]:
if i == len(nums) - 1:
yield [nums[j] for j in indices]
return
for j in range(i, len(nums)):
indices[i], indices[j] = indices[j], indices[i]
yield from perm(indices[:], i + 1)
indices[i], indices[j] = indices[j], indices[i]

indices = list(range(len(nums)))
return list(perm(indices[:], 0))
```

- https://discord.com/channels/1084280443945353267/1233603535862628432/1237775088762490900
- https://github.com/goto-untrapped/Arai60/pull/16


スタックでも書いてみる。

```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if not nums:
return [[]]
stack = [(list(range(len(nums))), 0)]
all_permutations = []
while stack:
indices, index = stack.pop()
if index == len(nums) - 1:
all_permutations.append([nums[i] for i in indices])
continue
for i in range(index, len(nums)):
indices[index], indices[i] = indices[i], indices[index]
stack.append((indices[:], index + 1))
indices[index], indices[i] = indices[i], indices[index]
return all_permutations
```

- https://discord.com/channels/1084280443945353267/1225849404037009609/1235272786474303560
- https://github.com/SuperHotDogCat/coding-interview/pull/12
- https://discord.com/channels/1084280443945353267/1201211204547383386/1228915333310713856
- https://github.com/shining-ai/leetcode/pull/50
- https://discord.com/channels/1084280443945353267/1200089668901937312/1220995877624217620
- https://github.com/hayashi-ay/leetcode/pull/57


## 3rd

全体的に命名をサボりがちかも。ただこのくらいなら読めるんじゃないか?という気もしてこうなっている (が、後で読んだら読めないとかあるのかもしれない)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

個人的には読めるんですが、(Step1のラストのやつのようなややこしいやつは変数名よりむしろ、書かれているようなコメントの方が欲しい気分)他の人の意見も気になります。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分も読めると思いました。
関数名、perm, permsだけ少し気になりました。(丁寧に書くと、permutation_generatorとかでしょうか?)

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.

generatorだとクラス名っぽい気がするので、自分が書くならgenerate_permutationsとかですかね。
ただ名前の長さの割に得られる情報が少ない気がしまして (permが良いというわけではなく、妥協に近いんですが)。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

上から読んでいった場合に何するものなのかちょっとわかりづらいと感じた位でした。また、右記も同意です。ただ名前の長さの割に得られる情報が少ない気がしまして


```py
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if not nums:
return [[]]

def perm(indices: list[int], i: int) -> Iterator[list[int]]:
if i == len(nums) - 1:
yield [nums[j] for j in indices]
return
for j in range(i, len(nums)):
indices[i], indices[j] = indices[j], indices[i]
yield from perm(indices[:], i + 1)
indices[i], indices[j] = indices[j], indices[i]

indices = list(range(len(nums)))
return list(perm(indices[:], 0))
```