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
269 changes: 269 additions & 0 deletions 206. Reverse Linked List.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
# step 1
stackを用いるものと、そのままリストを辿っていくものを思いついたので実装した。
再帰でもできるが、nodeの最大数(5000)がpythonのrecursion limit(1000)を超えるので、
実装はしなかった。

stackを用いるもの

「stackの末尾からノードを取ってきて、後ろに繋いでいけば逆順になります」はわかりやすいと思う。

nodeという変数を二つのwhile文で使い回してしまっているのが若干気持ち悪い。
stackにノードを入れるときと、ノードを取り出すときとで、どちらも今注目しているノードくらいの意味で
nodeという変数を使っているため、これくらいなら許容範囲かと判断した。後半のwhile文ではnodeはなくても良いかも。

- time complexity: O(n)
- space complexity: O(n) (auxiliary space: O(n))
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
stack = [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.

自分はstackにNoneを入れるのが気になりました。反転した後の末尾のnextをNoneにするためだと思うのですが、その意図を汲むのが難しいように思います。素直に2つ目のwhileを抜けた後にNoneを代入するので良いかなと思いました。

node = head
while node is not None:
stack.append(node)
node = node.next

dummy_head = ListNode()
tail = dummy_head
while stack:
node = stack[-1]
stack.pop()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

node = stack.pop()という書き方もあるなと思いました。


tail.next = node
tail = tail.next
return dummy_head.next
```

そのままリストを辿る解法

- time complexity: O(n)
- space complexity: O(n) (auxiliary space: O(1))

previousを返すのが少し気持ち悪い。nodeに注目していたのに、返すものがpreviousであることに不整合を感じる。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

仕事をループの途中で引き継いだとしましょう。

これが node と previous ね、という書き置きがあったら何言っているんだって感じじゃないですか。

先頭からひっくり返していって「まだひっくり返していない部分」と「もうひっくり返した部分」という引き継ぎになるでしょう。

node は、まだ一番注目しているくらいの意味でいいでしょうが、previous は、意味ではなくて操作の順番から付いている名前ですね。 意味と形式のどちらから名前を付けるかは状況次第で、なるべくパズルを作らないようにつけましょう。

```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
previous = None
node = head
while node is not None:
next_node = node.next

node.next = previous
previous = node
node = next_node

return previous
```
# step 2
- https://github.com/katataku/leetcode/pull/7/files
- 再帰の解法が書かれている
- 後ろからひっくり返していく解法(現在注目しているlistの先頭を引数に取り、そのリストを逆順にしたリストの先頭と末尾を返す)
- 先頭ノードを付け替えていく解法(現在注目しているlistの先頭と、逆順にしたlistのheadを引数に取り、オリジナルの先頭を逆順にしているリストの先頭にくっつける解法)
- https://github.com/konnysh/arai60/pull/7
- previous, nextの命名に関して、前後というのは作業者が見ているものに対しての相対的な位置でしかなく、真にその変数が持つ性質を反映したものではない。その変数に何を期待するかを命名に入れた方がいい。previousだったら「構築してるノードの先頭」で、nextは「未処理ノードの先頭」みたいな感じ。そうすればstep1でpreviousを返すという違和感を解消できる。


Iterative solution without stack
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
reversed_head = None
node = head

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

return reversed_head
```

Iterative solution with stack
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
stack = [None]
node = head
while node is not None:
stack.append(node)
node = node.next

reversed_dummy_head = ListNode()
reversed_tail = reversed_dummy_head
while stack:
node = stack[-1]
stack.pop()

reversed_tail.next = node
reversed_tail = reversed_tail.next
return reversed_dummy_head.next
```

Reversive solution (後ろからひっくり返す)
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(
head: ListNode | None
) -> Tuple[ListNode | None, ListNode | None]:
if head is None:
return None, None
if head.next is None:
return head, head

reversed_head, reversed_tail = reverse_list_helper(head.next)
reversed_tail.next = head

reversed_tail = reversed_tail.next
reversed_tail.next = None
return reversed_head, reversed_tail
return reverse_list_helper(head)[0]
```

Recursive solution (先頭ノードを付け替える)
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(
head: ListNode | 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.

Optional[ListNode] のほうがよく見ますかね。

reversed_head: ListNode | None) -> ListNode | None:
if head is None:
return reversed_head
next_head = head.next
head.next = reversed_head
reversed_head = head
return reverse_list_helper(next_head, reversed_head)
return reverse_list_helper(head, None)
```

inner functionのtype annotaionはpython3.10+のものとした。
public APIとかではないのでつける必要はない気がした。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

拝見して、僕も気になったので調べてみました。
public APIsだと必須だけど、それ以外は必要に応じてって感じなんですね。。難しい

At least annotate your public APIs.

https://google.github.io/styleguide/pyguide.html#3191-general-rules

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

最近では、ある程度大規模な開発になると型を書いているように思いますね。


# step 3
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
reversed_head = None
node = head
while node is not None:
next_node = node.next
node.next = reversed_head
reversed_head = node
node = next_node
return reversed_head
```

```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(head):
if head is None:
return None, None
if head.next is None:
return head, head
reversed_head, reversed_tail = reverse_list_helper(head.next)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

先に head.next を別の変数に入れて、head.next = None にしてから再帰したほうが、概念的には分かりやすい気がします。

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.

コメントありがとうございます。

reversed_tailにheadを取り付ける際に、head.next = Noneとするのも、reversed_tailを先に進めてからreversed_tail.next = Noneとするのも、少し違和感を感じていたので、この説明かなり腑に落ちました。


reversed_tail.next = head
reversed_tail = reversed_tail.next
reversed_tail.next = None
return reversed_head, reversed_tail
return reverse_list_helper(head)[0]
```

# step 4
コメントまとめ
- 大規模開発ではtype annotationを入れることが多い
- ListNode | NoneよりOptional[ListNode] のほうがよく見る
- ループの途中で仕事を引き継いだ時に、パズルとならないような変数名をつける

stackを使う解法
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
stack = []
node = head
while node is not None:
stack.append(node)
node = node.next

dummy_head = ListNode()
tail = dummy_head
while stack:
tail.next = stack.pop()
tail = tail.next
tail.next = None
return dummy_head.next
```

stackを使わないiterative
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
reversed_head = None
node = head
while node is not None:
next_node = node.next
node.next = reversed_head
reversed_head = node
node = next_node
return reversed_head
```

後ろから作るrecursiveな解法
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(
head: Optional[ListNode]
) -> Tuple[Optional[ListNode], Optional[ListNode]]:
if head is None:
return None, None
if head.next is None:
return head, head
tail_list = head.next
head_node = head
head_node.next = None

reversed_head, reversed_tail = reverse_list_helper(tail_list)
reversed_tail.next = head_node
reversed_tail = reversed_tail.next
return reversed_head, reversed_tail
return reverse_list_helper(head)[0]
```

先頭を付け替えていくrecursiveな解法
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse_list_helper(
head: Optional[ListNode],
reversed_head: Optional[ListNode]
) -> Optional[ListNode]:
if head is None:
return reversed_head
tail_list = head.next
head.next = reversed_head
reversed_head = head
return reverse_list_helper(tail_list, reversed_head)
return reverse_list_helper(head, None)
```

stackに積むときにnodeのリンクをバラバラにする
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
node = head
stack = []
while node is not None:
next_node = node.next
node.next = None
stack.append(node)
node = next_node

dummy_head = ListNode()
node = dummy_head
while stack:
node.next = stack.pop()
node = node.next
return dummy_head.next
```