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
168 changes: 168 additions & 0 deletions 31. Next Permutation.md
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if 文の中で j を使っていないため、 j のスコープを短くするため、 if 文と j の定義を入れ替えたほうがよいと思います。

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):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この関数は再利用する可能性がありそうなのでutilのようなモジュールに書くのも選択肢だと思いました。

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.

確かに、これを書いているときにライブラリにないことに驚きました

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
Comment on lines +152 to +156
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.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.4qnnfvpo8ij5

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.

Leetcode上だと短い関数は関数の定義を探す必要があるので、読みにくく感じて避けたのですが、
エディタ上なら分割したほうが読みやすそうです。

if i == -1:
nums.reverse()
return
j = nums_length - 1
while nums[j] <= nums[i]:
j -= 1
Comment on lines +160 to +162
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これも名前をつけると、後ろから nums[i] 以上であるようなものを見つけるということですね。

nums[i], nums[j] = nums[j], nums[i]
reverse_in_range(i + 1, nums_length - 1)
return
```

まあ、何をやっているかは伝わるのではないでしょうか。