diff --git a/31. Next Permutation.md b/31. Next Permutation.md new file mode 100644 index 0000000..f04ec92 --- /dev/null +++ b/31. Next Permutation.md @@ -0,0 +1,168 @@ +# 進め方 + +Step1 : 問題を解く。 + +Step2 : 他の人のPRを参照し、コメントする。 + +Step3 : 3回続けてエラーが出ないように書く。ドキュメントを参照する。 + +# 実践 + +## Step1 + +### 思考ログ + +[1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1] + +基本的には末尾をとって、それより小さいところに入れればよいか。 + +いや違うか。 + +これは法則性がわかれば解けるタイプの問題だと思うんだけどな。 + +sst → sts + +sbt → tsb + +1234 1243 1324 1342 1423 1432 2134 + +末尾がどこに入るか考えて、ソートすればよいか。 + +なんか再帰でも解けそう。 + +```python +class Solution: + def nextPermutation(self, nums: List[int]) -> None: + i = len(nums) - 1 + while i >= 0: + j = i + while j >= 0: + if nums[j] >= nums[i]: + j -= 1 + continue + nums[i], nums[j] = nums[j], nums[i] + sorted_nums = sorted(nums[j+1:]) + for k in range(j + 1, len(nums)): + nums[k] = sorted_nums[k - j - 1] + return + i -= 1 + else: + nums.sort() + return +``` + +[4,2,0,2,3,2,0]でエラー。うーんアルゴリズム自体に問題がありそう。 + +一部だけソートのやり方もわからなかった。 + +Geminiに質問 + +発想は悪くなさそう。 + +| ステップ | やっていること | なぜ正しいか | +| --- | --- | --- | +| 1. `i` の発見 | 増加が崩れる場所を探す | 増加が終わったらそれ以上小さい順列がない | +| 2. `j` と交換 | 少しだけ大きくする | 最小の差で次の順列に近づけるため | +| 3. reverse | 末尾を最小にする | 次に来る最小の順列にしたいから | + +```python +class Solution: + def nextPermutation(self, nums: List[int]) -> None: + i = len(nums) - 2 + while i >= 0: + if nums[i] < nums[i + 1]: + break + i -= 1 + j = len(nums) - 1 + if i < 0: + nums.reverse() + return + while nums[j] <= nums[i]: + j -= 1 + nums[i], nums[j] = nums[j], nums[i] + + hi = len(nums) - 1 + lo = i + 1 + while lo < hi: + nums[hi], nums[lo] = nums[lo], nums[hi] + hi -= 1 + lo += 1 + return +``` + +ステップに分ければそこまで難しくないですね。 + +辞書順は前が強いので、降順でソートされていたら大きくしようがない。 + +x,x,x,2,1 + +x,x,3,2,1 + +x,4,3,2,1 + +2,4,3,2,1 + +降順になっていないところを見つけて、そこをそいつより大きい数で入れ替えれば大きくなる + +あとは最小のものにしたいので、ソートすればよい。 + +降順になっているので、そのままswapでよい。 + +O(N) + +## Step2 + +### 同じ問題を解いた人のプルリクを見る + +https://github.com/olsen-blue/Arai60/pull/59/files + +- 関数にわけている。でも変数名が難しいらしい。名前つけられないもんなあ。 +- 短い関数をたくさん作るよりもコメントでの保管のほうが今回は良さそう。 + +> これは少しややこしい話だと思います。まず、原理的に、コンピュータがコードが到達可能か不可能かを判定することは不可能です。これはチューリングマシンの停止性問題は判定できないことからいえます。 +一方で、Java や Rust などは、コンパイル時に型のチェックを真面目にしていて、到達不能なコードかはある程度判定します。しかし、不完全です。このため、正確に考えると到達不可能な箇所であったとしても、コンパイラには分からない場合が多々あります。こういったときに、そこの行に例外を書くことでコンパイル可能になります。何を言っているかというと、上のような事情ならば、例外を書くのはいいですが、そうでなければ、私はデッドコードは基本的に書かないほうがよいと思っています。(せいぜいコメントでいいでしょう。) +> +- なるほど。確かに例外の内容とか起きるまで注意深く読まないですし。 + +https://github.com/hroc135/leetcode/pull/53 + +> これ、数が10-20くらいだとおそらく速度はそう変わりません。分岐予測が効くので線形に舐めるのは思っているよりも速いのです。すでに pivot を探すのに線形時間かかっていることもあり、私はこれはコードの単純さを取る場面だと判断すると思います。 +> +- ボトルネックにならないなら放っておくのも手ですね。 +- [https://ja.wikipedia.org/wiki/分岐予測#](https://ja.wikipedia.org/wiki/%E5%88%86%E5%B2%90%E4%BA%88%E6%B8%AC#) +- ここらへんのCSの知識不足を感じます。 + +## Step3 + +### 3回連続で再現 + +やっていることは難しくないがなぜ動くかわからないときは、変数名に意味を乗せるよりもコメントで説明してしまったほうが良いという考えで、適当な変数名で書いてみる。 + +```python +class Solution: + def nextPermutation(self, nums: List[int]) -> None: + def reverse_in_range(lo, hi): + while lo < hi: + nums[lo], nums[hi] = nums[hi], nums[lo] + lo += 1 + hi -= 1 + return + + nums_length = len(nums) + i = nums_length - 2 + while i >= 0: + if nums[i] < nums[i+1]: + break + i -= 1 + if i == -1: + nums.reverse() + return + j = nums_length - 1 + while nums[j] <= nums[i]: + j -= 1 + nums[i], nums[j] = nums[j], nums[i] + reverse_in_range(i + 1, nums_length - 1) + return +``` + +まあ、何をやっているかは伝わるのではないでしょうか。