From 67dee663db881c3a39bd4965cb99d0a3e1ed4586 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 24 Apr 2026 06:35:27 +0900 Subject: [PATCH] 31. Next Permutation --- 31.Next-Permutation/memo.md | 50 +++++++++++++++++++++++++++++++++++++ 31.Next-Permutation/sol1.py | 19 ++++++++++++++ 31.Next-Permutation/sol2.py | 19 ++++++++++++++ 31.Next-Permutation/sol3.py | 17 +++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 31.Next-Permutation/memo.md create mode 100644 31.Next-Permutation/sol1.py create mode 100644 31.Next-Permutation/sol2.py create mode 100644 31.Next-Permutation/sol3.py diff --git a/31.Next-Permutation/memo.md b/31.Next-Permutation/memo.md new file mode 100644 index 0000000..5977be1 --- /dev/null +++ b/31.Next-Permutation/memo.md @@ -0,0 +1,50 @@ +# 31. Next Permutation + +Next Permutationのアルゴリズムを知っていたので解けた: sol1. + +`nums[ascending_index + 1 :] = sorted(nums[ascending_index + 1 :])` +と書くべきところを +`nums[ascending_index + 1 :].sort()` +と書いてしまい一度間違えた。 + +配列のスライスはコピーを作ることを確認しておく + +### コメント集 + +https://discord.com/channels/1084280443945353267/1237649827240742942/1353878925117227113 + +> これを見て、first_increasing_order_index の初期化がここで終了したという理解は構造からは得られないと思うんですよね。 + +なるほど、初期化の意図が分かりづらくなってしまうのは納得した。これは他の処理でも出てきそう。 +改善後: sol2.py + +ついでに少し処理をまとめる: sol3.py + +> C++ 実装を読みましょう。 + +https://discord.com/channels/1084280443945353267/1201211204547383386/1232011836543467660 + +https://en.cppreference.com/cpp/algorithm/next_permutation + +Pythonより簡潔な印象 + +https://github.com/olsen-blue/Arai60/pull/59#discussion_r2030531369 + +> ひっくり返すポイントを見つけるのに、自乗のオーダーがかかっているので、これの最悪計算量は O(n^2) ですね。 + +> しかし、償却計算量を考えると、そうではなさそうです。 +> 長さ k がひっくり返る確率は、k/(k+1) 1/k! (ただし、k=n のときには 1/k!)でしょうか。 + +> これに k^2 をかけて期待値を計算すると、∑ k^2 / k! で上から抑えられるのですが、これは発散しないので定数時間ですね。 + +償却計算量という言葉は知らなかった + +> 償却計算量は、ある最悪な入力の列に対しての振る舞いのことです。 たとえば、list に append していくと、たまに、メモリーのリアロケーションで配列長かかりますが、大きさが倍々に増えていく場合(倍でなくても等比級数的ならばよい)は、reallocation のコストは定数で平均すると抑えられます。 + +一連の操作に対する平均の計算量でこの一連の操作が最悪の場合を考える、と理解した + +行間を埋めくださっている方がいた + +https://github.com/mamo3gr/arai60/blob/31_next-permutation/31_next-permutation/memo.md + +> 1文目:for i と for j の二重ループなので、ループ内の早期終了する条件を満たさない場合 O(N^2) になる。 2文目:長さ k のランダムな順列を考えると、完全降順になる確率は 1/k!. k 個は完全降順だが、k+1 個はそうでない確率は、1/k! - 1/(k+1)!. これを変形すると k(k+1) 1/k! になる。 3文目:O(N^2) から、長さ k を処理する計算量=コストを k^2 として、先程の確率に掛け算して期待値を求める。 すると k^2 x k/(k+1) x 1/k! <= k^2/k!. ∵ k/(k+1) < 1. k^2/k! の分母と分子を見ると、k^2よりもk!の方が遥かに速く増える(=発散しない)。 極限を取ると 2e になるらしい。 diff --git a/31.Next-Permutation/sol1.py b/31.Next-Permutation/sol1.py new file mode 100644 index 0000000..188ef59 --- /dev/null +++ b/31.Next-Permutation/sol1.py @@ -0,0 +1,19 @@ +class Solution: + def nextPermutation(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + pivot = len(nums) - 2 + while pivot >= 0: + if nums[pivot] < nums[pivot + 1]: + break + pivot -= 1 + if pivot < 0: + nums.sort() + return + for index in range(len(nums) - 1, pivot, -1): + if nums[pivot] < nums[index]: + nums[pivot], nums[index] = nums[index], nums[pivot] + break + nums[pivot + 1 :] = sorted(nums[pivot + 1 :]) + return diff --git a/31.Next-Permutation/sol2.py b/31.Next-Permutation/sol2.py new file mode 100644 index 0000000..63ec752 --- /dev/null +++ b/31.Next-Permutation/sol2.py @@ -0,0 +1,19 @@ +class Solution: + def nextPermutation(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + pivot = -1 + for pivot in range(len(nums) - 2, -1, -1): + if nums[pivot] < nums[pivot + 1]: + break + pivot -= 1 + if pivot < 0: + nums.sort() + return + for index in range(len(nums) - 1, pivot, -1): + if nums[pivot] < nums[index]: + nums[pivot], nums[index] = nums[index], nums[pivot] + break + nums[pivot + 1 :] = sorted(nums[pivot + 1 :]) + return diff --git a/31.Next-Permutation/sol3.py b/31.Next-Permutation/sol3.py new file mode 100644 index 0000000..46d6cf8 --- /dev/null +++ b/31.Next-Permutation/sol3.py @@ -0,0 +1,17 @@ +class Solution: + def nextPermutation(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + pivot = -1 + for pivot in range(len(nums) - 2, -1, -1): + if nums[pivot] < nums[pivot + 1]: + break + pivot -= 1 + if pivot >= 0: + for index in range(len(nums) - 1, pivot, -1): + if nums[pivot] < nums[index]: + nums[pivot], nums[index] = nums[index], nums[pivot] + break + nums[pivot + 1 :] = sorted(nums[pivot + 1 :]) + return