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
181 changes: 181 additions & 0 deletions reverse-linked-list/main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
## Step1
stackに全ノードをためておき, 今度はstackが空になるまで後ろにつなげていけばいい.
時間計算量はO(N)になる.
時間: 10分くらい.

```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
node = head
stack = []
Comment on lines +7 to +10
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 に格納していくのに違和感があったため、自分なら以下のように、一度繋がりを切ってから stack に格納する書き方をすると思いました。

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 = ListNode()
        tail = dummy
        while stack:
            tail.next = stack.pop()
            tail = tail.next

        return dummy.next

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 node is not None:
stack.append(node)
node = node.next

dummy = ListNode()
tail = dummy
while stack:
tail.next = stack.pop()
tail.next.next = None
tail = tail.next

return dummy.next
```
`tail`より`reversed_node`あたりが良いか?

# Step2 follw up, 他の人のコードを読む.

次にフォローアップで再帰を用いて書いてみる.
以下のように記述したが間違い. head.next以降を逆順に並び替えたものの後ろに加えるという発想はあっていたが, これだと末尾を返すことになってしまう.
```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
Comment on lines +29 to +32
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 を取り付ける位置が違っている、という表現の方がが正確なのかなと感じます。以下で書くと、正常に動くと思います。

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 から逆順にすると、処理後に head.next は tail になるので、head.next の次を head にしてあげれば良い。
        head.next.next = head
        head.next = None
        return reversed_head

if head is None or head.next is None:
return head

reversed_head = self.reverseList(head.next)
head.next = None
reversed_head.next = head
return reversed_head
```

https://discord.com/channels/1084280443945353267/1231966485610758196/1239417493211320382
https://github.com/goto-untrapped/Arai60/pull/27/files
再帰については2つの考え方がある.
1. 頭からn番目まで逆順にしたリストを渡して, 全体が逆順になったものを返却してもらう.
2. 何も渡さず, n番目以降を逆順にしたものを返却してもらう.

1は
2はheadとhead.nextに分離して, head.nextを再帰的に逆順にしたもののtailにheadをくっつける. 最終的には逆順リストの頭を返さないといけないので, 再帰関数ではheadも返すようにし, 最終的にこれを返却する.

```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverseListHelper(node):
# nodeが最後の要素のとき.
Comment on lines +52 to +55
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の実装で、末尾再帰で書いている方が考え方 1 実装かなと思います。こちらでは、「自分が持っている部分は渡さず、自分が持っている部分から先(n番目以降)を逆順にしたものを返却してもらって」、返却してもらったものをもとに自分で処理を行なっているためです。

if node.next is None:
return node, node

node_next = node.next
node.next = None
new_head, new_tail = reverseListHelper(node_next)
new_tail.next = node

return new_head, node

# edge case: 空リストのときは先に処理する.
if not head:
return head

head, _tail = reverseListHelper(head)
return head
```

```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverseListHelper(node):
# nodeが最後の要素のとき.
if node.next is None:
return node, node

node_next = node.next
# node.next = None
new_head, new_tail = reverseListHelper(node_next)
# こっちのほうがいいかも. 非破壊.
node.next = None
new_tail.next = node

return new_head, node

# edge case: 空リストのときは先に処理する.
if not head:
return head

head, _tail = reverseListHelper(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.

未使用変数は1つだけであり、他の未使用変数と区別する必要が無いことから単に_にすると思いました。

Suggested change
head, _tail = reverseListHelper(head)
head, _ = reverseListHelper(head)

return head
```

また, `new_tail`は`node.next`になるので, 以下のように書ける.
```diff
@@ def reverseListHelper(node):
if node.next is None:
- return node, node
+ return node

- new_head, new_tail = reverseListHelper(node_next)
+ new_head = reverseListHelper(node_next)
+ node.next.next = node
node.next = None
- new_tail.next = node
- return new_head, node
+ return new_head
```

また, 2のやり方は以下のように書ける.
これは**末尾再帰**
```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverseListHelper(reversed_head, rest_node):
if rest_node.next is None:
rest_node.next = reversed_head
return rest_node
Comment on lines +120 to +123
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_head が「すでに逆順にしたノードの先頭」、rest_node が「まだ逆順にしていないノードの先頭」であることを踏まえると、rest_node.next をもとに終了を判定するのではなく、rest_node がなくなればその時点で逆順にする処理は終わっているため、そのまま reversed_head を返す方がわかりやすいのかなと思います。

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

        if not head:
            return head

        return reverseListHelper(None, head) 


rest_node_next = rest_node.next
rest_node.next = reversed_head
return reverseListHelper(rest_node, rest_node_next)

if not head:
return head

return reverseListHelper(None, head)
```


空間計算量がO(1)になるやりかたもある.

```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
node = head
# 新たにheadを定義. これが返り値になる.
head = None

while node is not None:
# node.nextを一時保存する.
temp_node = node.next
# 現在のnodeの次にheadが来るようにする.
node.next = head
# headのポインタを更新.
head = node
# nodeを次に進める.
node = temp_node

return head
```
### iterativeとrecursionの比較
iterative版 => ポインタを一つずつ進め, 反転済み部分にconcatしながら一つの連結リストを作成する操作. foldl(左畳み込み)に相当する.
2の再帰(末尾再帰)=> iterative版と同じロジック. 関数間 / ループ間で渡すのはすでに逆順にした部分と残りの部分.
1の再帰 => ポインタを一つずつ進め, 反転していない前半の部分を反転済みの後半の部分にくっつける操作. すなわちfoldrに相当.


## Step3
通常の再帰で書く
```py
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverseListHelper(node):
if node.next is None:
return node

new_head = reverseListHelper(node.next)
node.next.next = node
node.next = None

return new_head
if not head:
Comment on lines +176 to +177
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定義部分の区別がつきやすいよう、ここには改行を入れたいなと思いました。

Suggested change
return new_head
if not head:
return new_head
if not head:

return head

return reverseListHelper(head)
```