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
226 changes: 226 additions & 0 deletions 206. Reverse Linked List/206. Reverse Linked List.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
問題文: https://leetcode.com/problems/reverse-linked-list/description/

# step1: 何も見ないで書く(制限時間5分)
- ノードを前から順に積み上げ、積み上げたノードを上から順につなぎなおせば逆順にできる。
- 時間計算量O(n), 空間計算量O(n)。
- ループで実装するのが一番簡単な気がする。
- 再帰を使ったらスタック無しで実装できそうだけど、どんな手順になるかはすぐに思いつかない。
- 妥協案として、ノードをスタックしてから再帰的に逆順のリストを作る方法にした。
Comment on lines +6 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰は結局関数呼び出しのスタックであり、明示的なスタック + loop実装に対する糖衣構文なので、この順番で発想されるのは不思議な感じがします。

私が実装を考えるときは、再帰なら実装できる -> スタック + loopでも実装できないだろうか、という順番ですね。

fyiで、再帰関数のよる関数呼び出しのスタックは明示的なスタックと比べ、引数のコピー、呼び出し元の位置の記憶などが追加で必要なので一般的に重くなります。

また、デバッグがしづらかったり、pythonのデフォルトの再起上限は大抵1000程度(leetcodeは引き上げられているそうです)などの事情で、通常は再帰関数よりloop実装のが好まれます。

Copy link
Copy Markdown

@nodchip nodchip Oct 25, 2025

Choose a reason for hiding this comment

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

糖衣構文は、プログラミング言語において、複雑なイディオムを、簡単に書けるようにする機能のことを表すように思います。再帰はスタック + loop を簡単に書けるようにしているようには感じないため、糖衣構文というのは違和感があります。スタック + loop 実装は、再帰でも書ける、別実装である、等価である、相互変換変換できる、くらいに表現したほうが良いと思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ありがとうございます。

ご指摘に異存ありません。用語のスコープに気をつけます、失礼しました...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

カタカナ語でスタックという場合、名詞の意味か、動作が止まることを表す stuck の意味で使うように思います。スタックに積むと表現したほうが、スムーズに伝わるように思います。


## 1-1: ループ
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None:
return head
Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この2行を省いても正しく動きますね。

番兵を使わない場合はif head is Noneの方は残すと思いますが、番兵を使われているのでこれも不要ということですね。

以降の実装についても同様です。


node = head
reversed_nodes = [None]
while node is not None:
reversed_nodes.append(node)
node = node.next

reversed_head = reversed_nodes.pop()
node = reversed_head
while reversed_nodes:
node.next = reversed_nodes.pop()
node = node.next

return reversed_head

```

## 1-2: 再帰
- 行きがけの再帰で実装した。帰りがけへの書き換えも可能(reversed_headも関数内でpopして作り、popしたノードをreturnする)だが省略する。
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def get_reversed_list(node, stacked_nodes):
if not stacked_nodes:
return None
node.next = stacked_nodes.pop()
get_reversed_list(node.next, stacked_nodes)

if head is None or head.next is None:
return head
Comment on lines +49 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分は入力例外を弾く処理はinner functionの定義より上に書くようにしているのですが、趣味の範囲かもしれません。

inner functionは例外的な入力を弾いた前提で使う予定ですよ、というのを読み手に伝えたいからそうしています。


node = head
reversed_nodes = [None]
while node is not None:
reversed_nodes.append(node)
node = node.next

reversed_head = reversed_nodes.pop()
get_reversed_list(reversed_head, reversed_nodes)
return reversed_head

```

# step2: 他の人を参考にコードを整える・コメントを予測する
## 典型コメント集
https://discord.com/channels/1084280443945353267/1231966485610758196/1239417493211320382
- 再帰で繋ぎ変えるときの手順について。実際の処理はループの方が簡単だと思うが、再帰の方が手順を考える訓練になると感じる。

https://github.com/TORUS0818/leetcode/pull/9/files
- 再帰での繋ぎ変えを検討している。繋ぎ変えの処理で変数がお手玉のように入れ替わるので読みにくい。

https://github.com/goto-untrapped/Arai60/pull/27/files#diff-46bd7fda9ce7231cf7f817c639106d9dda840171202428e02291f5d39c172801
- ループを使った繋ぎ変え。これもお手玉っぽさがあるので、ループか再帰かよりも仕事の引継ぎという発想で処理を組み立てられているかが重要そう。

https://github.com/katataku/leetcode/pull/7/files
- 再帰を2通り試している。行きがけだと元のリストの先頭から繋ぎ変えていく処理で、帰りがけだと元のリストの末尾から繋ぎ変える処理になる。

https://github.com/tarinaihitori/leetcode/pull/6/files
- 通常のpython環境で再帰を使用できるかを再帰回数の上限という点から検討している。再帰を使うときに何を気にすればいいのかわかっていないので参考になった。

https://github.com/quinn-sasha/leetcode/pull/7/files#r1948355100
- スタックに積む際にノードのリンクを切る方法。私のstep1は単にリストをスタックに入れただけでリンクは切っていなかったため、逆順リストの最後尾の処理が少し面倒になっていた。これなら処理がシンプル。

https://discord.com/channels/1084280443945353267/1355246975309844550
- リンクの逆転の仕方は実は3通りくらいあるという話。


## 直近のコード
https://github.com/docto-rin/leetcode/pull/7/files#r2403779253
- 入力されるリストを壊してもいいのかどうか。どう判断すべきか分からない。全ノードを逆転させることがわかっているから、入力を壊してもすぐに復元できそうな気がする。
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://discordapp.com/channels/1084280443945353267/1421920158506549470/1426880686064795751


https://github.com/MasukagamiHinata/Arai60/pull/3#discussion_r2393826274
-「一人で手でできる」→「シフトを組んで複数人でできる」→「機械にやらせられる」の順で仕事が難しくなるという話。
- 私は「一人の手作業」で止まりがちだった。再帰に苦手意識があるのも「複数人でシフトを組む」水準まで考える癖がないことが大きな原因になっていそう。

https://github.com/kt-from-j/leetcode/pull/7#discussion_r2393410573
- 練習の進め方について。私も可能な限りログを調べがちなのでこれからはスピードも意識する。
- 私の場合、問題を解くのに締め切りを設けてないのが良くない気がするので、着手してから2日以内に終わらせるなど自分で締め切りを設ける。

https://github.com/nanae772/leetcode-arai60/pull/8/files#r2324800451
- 再帰の上限をいじったときに生じうる問題について。ひらがなの読み書きだけ練習していると、こういう感覚は身につかないのだろう。

## 2-1: ループ
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None:
return head

node_in_original = head
reversed_nodes = []
while node_in_original is not None:
residual_head = node_in_original.next
node_in_original.next = None
reversed_nodes.append(node_in_original)
node_in_original = residual_head

dummy = ListNode(None)
node_in_reverse = dummy
while reversed_nodes:
node_in_reverse.next = reversed_nodes.pop()
node_in_reverse = node_in_reverse.next
return dummy.next

```

## 2-2: 行きがけの再帰(前から逆順にしていく)
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(disassembling_head: Optional[ListNode],reversed_head: Optional[ListNode]) -> Optional[ListNode]:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この行は空白込みで128文字あるようです。PEP8では、

Limit all lines to a maximum of 79 characters.

となっているので、改行することをお勧めします。

https://peps.python.org/pep-0008/#maximum-line-length

また、innfer functionなどにtype hintを入れてもあまり読み手の助けにならないという意見を見たことがあります。

if disassembling_head is None:
return reversed_head
node = disassembling_head
disassembling_head = disassembling_head.next
node.next = reversed_head
reversed_head = node
return reverse_list_helper(disassembling_head, reversed_head)

return reverse_list_helper(head, None)
Comment on lines +129 to +140
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

末尾再帰の形なので、容易にiterativeに書き直せます。(他言語だとコンパイラが自動でやってくれます)

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        disassembling_head = head
        reversed_head = None

        while disassembling_head is not None:
            node = disassembling_head
            disassembling_head = disassembling_head.next
            node.next = reversed_head
            reversed_head = node

        return reversed_head

空間計算量がO(1)で済み、時間計算量も定数倍で改善すると思います。


```

## 2-3: 帰りがけの再帰(後ろから逆順にしていく)
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(node: Optional[ListNode]) -> Tuple[Optional[ListNode], Optional[ListNode]]:
if node is None:
return None, None
if node.next is None:
return node, node
reversed_head, reversed_tail = reverse_list_helper(node.next)
node.next = None
reversed_tail.next = node
reversed_tail = reversed_tail.next
return reversed_head, reversed_tail

reversed_head, reversed_tail = reverse_list_helper(head)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reversed_tailは使わないので_で潰しておいてもいいかもしれません。趣味の範囲です。

return reversed_head
Comment on lines +146 to +160
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reversed_tailは再帰を呼び出した後でもnode.nextでアクセスできるので帰りがけの作業はreversed_tailなしでできます。reversed_headだけを返して書くこともできます。

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head is None or head.next is None:
            return head
        reversed_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return reversed_head


```

# step3: 3回連続ミスなしで書く(制限時間10分)

## 3-1: ループ
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None:
return head

node = head
reversed_nodes = []
while node is not None:
residual_head = node.next
node.next = None
reversed_nodes.append(node)
node = residual_head

dummy = ListNode(None)
reversed_tail = dummy
while reversed_nodes:
reversed_tail.next = reversed_nodes.pop()
reversed_tail = reversed_tail.next
return dummy.next
Comment on lines +182 to +186
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reversed_nodesから逐次popしてるので逆順に進んでることは十分伝わるかなと感じました。

1回目のwhileループのように、reversed_tailは単にnodeの方が読みやすい気がします。


```

## 3-2: 行きがけの再帰
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(separating_head, reversed_head):
if separating_head is None:
return reversed_head

node = separating_head
separating_head = separating_head.next
node.next = reversed_head
reversed_head = node
return reverse_list_helper(separating_head, reversed_head)

return reverse_list_helper(head, None)

```


## 3-3: 帰りがけの再帰
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(node):
if node is None or node.next is None:
return node, node

reversed_head, reversed_tail = reverse_list_helper(node.next)
node.next = None
reversed_tail.next = node
reversed_tail = reversed_tail.next
return reversed_head, reversed_tail

reversed_head, _ = reverse_list_helper(head)
return reversed_head

```