Conversation
Document various approaches to solving the reverse linked list problem, including iterative and recursive methods, with detailed explanations and code examples.
| if head is None or head.next is None: | ||
| return head |
There was a problem hiding this comment.
この2行を省いても正しく動きますね。
番兵を使わない場合はif head is Noneの方は残すと思いますが、番兵を使われているのでこれも不要ということですね。
以降の実装についても同様です。
| - ループで実装するのが一番簡単な気がする。 | ||
| - 再帰を使ったらスタック無しで実装できそうだけど、どんな手順になるかはすぐに思いつかない。 | ||
| - 妥協案として、ノードをスタックしてから再帰的に逆順のリストを作る方法にした。 |
There was a problem hiding this comment.
再帰は結局関数呼び出しのスタックであり、明示的なスタック + loop実装に対する糖衣構文なので、この順番で発想されるのは不思議な感じがします。
私が実装を考えるときは、再帰なら実装できる -> スタック + loopでも実装できないだろうか、という順番ですね。
fyiで、再帰関数のよる関数呼び出しのスタックは明示的なスタックと比べ、引数のコピー、呼び出し元の位置の記憶などが追加で必要なので一般的に重くなります。
また、デバッグがしづらかったり、pythonのデフォルトの再起上限は大抵1000程度(leetcodeは引き上げられているそうです)などの事情で、通常は再帰関数よりloop実装のが好まれます。
There was a problem hiding this comment.
糖衣構文は、プログラミング言語において、複雑なイディオムを、簡単に書けるようにする機能のことを表すように思います。再帰はスタック + loop を簡単に書けるようにしているようには感じないため、糖衣構文というのは違和感があります。スタック + loop 実装は、再帰でも書ける、別実装である、等価である、相互変換変換できる、くらいに表現したほうが良いと思います。
There was a problem hiding this comment.
ありがとうございます。
ご指摘に異存ありません。用語のスコープに気をつけます、失礼しました...
|
|
||
| ## 直近のコード | ||
| https://github.com/docto-rin/leetcode/pull/7/files#r2403779253 | ||
| - 入力されるリストを壊してもいいのかどうか。どう判断すべきか分からない。全ノードを逆転させることがわかっているから、入力を壊してもすぐに復元できそうな気がする。 |
There was a problem hiding this comment.
以下、自分が以前いただいたレビューコメントで、参考になればと思います。
入力を変更している点が気になりました。呼び出し側の視点に立って考えると、関数に渡した値が勝手に書き換えられているとびっくりすると思います。入力は原則変更しないか、変更する場合は関数コメントでそれを明記することをおすすめします。
https://discordapp.com/channels/1084280443945353267/1421920158506549470/1426880686064795751
| reversed_tail = dummy | ||
| while reversed_nodes: | ||
| reversed_tail.next = reversed_nodes.pop() | ||
| reversed_tail = reversed_tail.next | ||
| return dummy.next |
There was a problem hiding this comment.
reversed_nodesから逐次popしてるので逆順に進んでることは十分伝わるかなと感じました。
1回目のwhileループのように、reversed_tailは単にnodeの方が読みやすい気がします。
| ```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]: |
There was a problem hiding this comment.
この行は空白込みで128文字あるようです。PEP8では、
Limit all lines to a maximum of 79 characters.
となっているので、改行することをお勧めします。
https://peps.python.org/pep-0008/#maximum-line-length
また、innfer functionなどにtype hintを入れてもあまり読み手の助けにならないという意見を見たことがあります。
| 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) | ||
| return reversed_head |
There was a problem hiding this comment.
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| reversed_tail = reversed_tail.next | ||
| return reversed_head, reversed_tail | ||
|
|
||
| reversed_head, reversed_tail = reverse_list_helper(head) |
There was a problem hiding this comment.
reversed_tailは使わないので_で潰しておいてもいいかもしれません。趣味の範囲です。
| if head is None or head.next is None: | ||
| return head |
There was a problem hiding this comment.
自分は入力例外を弾く処理はinner functionの定義より上に書くようにしているのですが、趣味の範囲かもしれません。
inner functionは例外的な入力を弾いた前提で使う予定ですよ、というのを読み手に伝えたいからそうしています。
| class Solution: | ||
| def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
| def reverse_list_helper(disassembling_head: Optional[ListNode],reversed_head: Optional[ListNode]) -> Optional[ListNode]: | ||
| 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) |
There was a problem hiding this comment.
末尾再帰の形なので、容易に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)で済み、時間計算量も定数倍で改善すると思います。
| - ループで実装するのが一番簡単な気がする。 | ||
| - 再帰を使ったらスタック無しで実装できそうだけど、どんな手順になるかはすぐに思いつかない。 | ||
| - 妥協案として、ノードをスタックしてから再帰的に逆順のリストを作る方法にした。 |
There was a problem hiding this comment.
糖衣構文は、プログラミング言語において、複雑なイディオムを、簡単に書けるようにする機能のことを表すように思います。再帰はスタック + loop を簡単に書けるようにしているようには感じないため、糖衣構文というのは違和感があります。スタック + loop 実装は、再帰でも書ける、別実装である、等価である、相互変換変換できる、くらいに表現したほうが良いと思います。
| - 時間計算量O(n), 空間計算量O(n)。 | ||
| - ループで実装するのが一番簡単な気がする。 | ||
| - 再帰を使ったらスタック無しで実装できそうだけど、どんな手順になるかはすぐに思いつかない。 | ||
| - 妥協案として、ノードをスタックしてから再帰的に逆順のリストを作る方法にした。 |
There was a problem hiding this comment.
カタカナ語でスタックという場合、名詞の意味か、動作が止まることを表す stuck の意味で使うように思います。スタックに積むと表現したほうが、スムーズに伝わるように思います。
今回の問題: 206. Reverse Linked List
次回の問題: 703. Kth Largest Element in a Stream