-
Notifications
You must be signed in to change notification settings - Fork 0
create 206 reversed linked list #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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)。 | ||
| - ループで実装するのが一番簡単な気がする。 | ||
| - 再帰を使ったらスタック無しで実装できそうだけど、どんな手順になるかはすぐに思いつかない。 | ||
| - 妥協案として、ノードをスタックしてから再帰的に逆順のリストを作る方法にした。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| - 入力されるリストを壊してもいいのかどうか。どう判断すべきか分からない。全ノードを逆転させることがわかっているから、入力を壊してもすぐに復元できそうな気がする。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この行は空白込みで128文字あるようです。PEP8では、
となっているので、改行することをお勧めします。 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reversed_tailは使わないので |
||
| return reversed_head | ||
|
Comment on lines
+146
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
| ``` | ||
There was a problem hiding this comment.
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実装のが好まれます。
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
糖衣構文は、プログラミング言語において、複雑なイディオムを、簡単に書けるようにする機能のことを表すように思います。再帰はスタック + loop を簡単に書けるようにしているようには感じないため、糖衣構文というのは違和感があります。スタック + loop 実装は、再帰でも書ける、別実装である、等価である、相互
変換変換できる、くらいに表現したほうが良いと思います。There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます。
ご指摘に異存ありません。用語のスコープに気をつけます、失礼しました...