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
151 changes: 151 additions & 0 deletions 31. Next Permutation/31. Next Permutation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# 31. Next Permutation
## STEP1
- 何も見ずに解いてみる
- 46\. Permutations で実装していたので書けた。ただし辞書順末尾から先頭に戻すのを忘れていた。
```python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
if n == 0:
return
for i in reversed(range(n - 1)):
if nums[i] < nums[i + 1]:
break
else:
nums[:] = reversed(nums)
return

for swap_index in reversed(range(n)):
if nums[i] < nums[swap_index]:
break
nums[i], nums[swap_index] = nums[swap_index], nums[i]
nums[i + 1:] = reversed(nums[i + 1:])
```

## STEP2
### プルリクやドキュメントを参照
- n <= 1 のときに return の方がわかりやすい。
- https://docs.python.org/3/reference/compound_stmts.html#the-for-statement
- for 文が一回も回らない場合も else 節は実行されるので元のコードでも正しく動く。
- nums[:] = reversed(nums) は微妙。nums.reverse() の方が良い。
- nums[i + 1:] = reversed(nums[i + 1:]) は nums[i + 1:] = nums[i + 1:][::-1] でも良いが、どちらでも可と思う。しかし厳密には in-place でないか。
- 過去に実装例 https://cpprefjp.github.io/reference/algorithm/next_permutation.html を見ていたので上のようなコードになった。
- 各処理を関数にしているPRを読んで冗長では?と思ったが、各処理の意味を読み取らせる負担を読み手に押し付けているのでよくない。
https://discord.com/channels/1084280443945353267/1237649827240742942/1353878925117227113
- i の寿命が長いのに、変数名に意味がなく、初期化がわかりにくいという点が問題。
- 書き直してみる。
```python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
n = len(nums)
if n <= 1:
return

def rfind_pivot() -> int | None:
for i in reversed(range(n - 1)):
if nums[i] < nums[i + 1]:
return i
return None

def rfind_successor(pivot_index: int) -> int:
# There must exist an element greater than nums[pivot_index] to its right
for i in reversed(range(n)):
if nums[pivot_index] < nums[i]:
return i

pivot_index = rfind_pivot()
if pivot_index is None:
nums.reverse()
return

successor_index = rfind_successor(pivot_index)
nums[pivot_index], nums[successor_index] = nums[successor_index], nums[pivot_index]
nums[pivot_index + 1:] = reversed(nums[pivot_index + 1:])
```
- https://peps.python.org/pep-0008/#:~:text=Be%20consistent%20in%20return%20statements
- return expression がある場合には return None と明示的に書くべきとのこと。rfind_pivot では return None と書く。rfind_successor では正しい使われ方をすると末尾には到達しないので return None は書かない。コメントでも補足しておく。
- nums[pivot_index + 1:] = reversed(nums[pivot_index + 1:]) について
- reversed はイテレータを返すはずでどのように左辺にアサインされるかわからなかった。
- https://docs.python.org/3/library/functions.html#reversed
- <https://docs.python.org/3/reference/datamodel.html#object.__setitem__>
\_\_setitem__ が呼ばれて良い感じに処理されるのではと思った。
- https://github.com/olsen-blue/Arai60/pull/59/files
- range の step を -1 にして逆順にするの結構読みにくさを感じる。`range(n - 2, -1, -1)` と `reversed(range(n - 1))` では後者が好み。
- https://docs.python.org/3/library/functions.html#func-range
- https://docs.python.org/3/library/stdtypes.html#typesseq-range
- https://github.com/python/cpython/blob/3.13/Objects/rangeobject.c#L1181
- CPython には range_reverse という関数があり、`reversed(range)` の組み合わせをいい感じに (追加で多くの計算を要さずに) 処理してくれそう。
- 2重ループで書くのも面白いですね。
- https://en.cppreference.com/w/cpp/algorithm/next_permutation.html
- C++ の英語版refだと日本語版と違う実装例が載っている。std::is_sorted_until みたいな関数があるの意外です。関数名も自分ではつけないような感じで勉強になる。
## STEP3
### 3回ミスなく書く
```python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
n = len(nums)
if n <= 1:
return

def rfind_pivot() -> int | None:
for i in reversed(range(n - 1)):
if nums[i] < nums[i + 1]:
return i
return None

def rfind_successor(pivot_index: int) -> int:
# There must be an element greater than nums[pivoit_index] to its right
for i in reversed(range(n)):
if nums[pivot_index] < nums[i]:
return i

pivot_index = rfind_pivot()
if pivot_index is None:
nums.reverse()
return

successor_index = rfind_successor(pivot_index)
nums[pivot_index], nums[successor_index] = nums[successor_index], nums[pivot_index]
nums[pivot_index + 1:] = reversed(nums[pivot_index + 1:])
```

4分,3分,4分で3回Accept

3回目は reverse も関数として書きました。
```python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
n = len(nums)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

len(nums) のような定数時間でアクセスでき、かつ極めてシンプルなものをあえて変数に置くと却って読みにくくなるかもしれません。

if n <= 1:
return

def rfind_pivot() -> int | None:
for i in reversed(range(n - 1)):
if nums[i] < nums[i + 1]:
return i
return None
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ここで何を返すかは議論の余地があると思いますが、rfind という名前を使うなら built-in の規則に合わせたくなります。
具体的には -1 を返していますね。

https://docs.python.org/ja/3.13/library/stdtypes.html#str.rfind

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.

なるほど、その観点はありませんでした。個人的には -1 はインデックスとして成立してしまうので None を返したいと思って書きました。いずれにせよdocstringに書いた方が良いですね。ありがとうございます。


def rfind_successor(pivot_index: int) -> int:
# There must be an element greater than nums[pivot_index] to its right.
for i in reversed(range(n)):
if nums[pivot_index] < nums[i]:
return i

def reverse_in_range(left: int, right: int) -> None:
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1

pivot_index = rfind_pivot()
if pivot_index is None:
nums.reverse()
return

successor_index = rfind_successor(pivot_index)
nums[pivot_index], nums[successor_index] = nums[successor_index], nums[pivot_index]
reverse_in_range(pivot_index + 1, n - 1)
```