From 5722d770408bed16fdd781a3b86635788eccf3b0 Mon Sep 17 00:00:00 2001 From: tokuhirat <54652919+tokuhirat@users.noreply.github.com> Date: Thu, 14 Aug 2025 08:32:19 +0900 Subject: [PATCH] Create 46. Permutations.md --- 46. Permutations/46. Permutations.md | 169 +++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 46. Permutations/46. Permutations.md diff --git a/46. Permutations/46. Permutations.md b/46. Permutations/46. Permutations.md new file mode 100644 index 0000000..118545d --- /dev/null +++ b/46. Permutations/46. Permutations.md @@ -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 +``` +- 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 はとても書きにくい。自然な理解ができていないのだと思う。来週には書けなくなっていそう。