-
Notifications
You must be signed in to change notification settings - Fork 0
Create 46. Permutations.md #50
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,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: | ||
| 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
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. 私はこの部分が特に気に食わなくて、
とすると思います。
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. 私も読んでいる時 if else の部分は微妙だと思っていました。 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 resultsThere 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つの append をまとめてループの先頭に回すこともできますね。 j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]これも for の外に追い出せます。(どちらがいいかは難しいところ。)
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. そうですね。do-while のような処理をまとめる形で書くのが苦手だなと感じています。 |
||
| ``` | ||
| - 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 はとても書きにくい。自然な理解ができていないのだと思う。来週には書けなくなっていそう。 | ||
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.
使用中の num を set に入れておくと、時間計算量は下がりそうです。ただ、
1 <= nums.length <= 6のため、こちらのほうが速いかもしれません。