From 89ee4a31926735752b2503c384c5ff348568192a Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 14 May 2025 19:05:28 -0700 Subject: [PATCH 01/20] LeetCode 206: Add step 1 solutions --- leetcode/206/memo.md | 0 leetcode/206/step1_iterative.py | 19 +++++++++++++++++++ leetcode/206/step1_recursive.py | 25 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 leetcode/206/memo.md create mode 100644 leetcode/206/step1_iterative.py create mode 100644 leetcode/206/step1_recursive.py diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/206/step1_iterative.py b/leetcode/206/step1_iterative.py new file mode 100644 index 0000000..12bf76d --- /dev/null +++ b/leetcode/206/step1_iterative.py @@ -0,0 +1,19 @@ +# 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]: + prev = None + curr = head + + while curr: + next_ = curr.next + + curr.next = prev + prev = curr + + curr = next_ + + return prev diff --git a/leetcode/206/step1_recursive.py b/leetcode/206/step1_recursive.py new file mode 100644 index 0000000..4d141a6 --- /dev/null +++ b/leetcode/206/step1_recursive.py @@ -0,0 +1,25 @@ +# 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]: + def reverse_list_helper( + node: Optional[ListNode], + ) -> tuple[ + Optional[ListNode], Optional[ListNode] + ]: # reversed_tail, reversed_head + if node is None: + return None, None + + if node.next is None: + return node, node + + reversed_tail, reversed_head = reverse_list_helper(node.next) + reversed_tail.next = node + node.next = None # to avoid cycle at the end of the list + + return node, reversed_head + + return reverse_list_helper(head)[1] From ac64395daec5c9c2f39a9ae600655a7cb1a06610 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 14 May 2025 20:45:56 -0700 Subject: [PATCH 02/20] LeetCode 206: Reviewed pull requests --- leetcode/206/memo.md | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index e69de29..58a3997 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -0,0 +1,59 @@ +# Step 2 + +## 他の方々のPRを見る + +[h1rosakaさんのPR](https://github.com/h1rosaka/arai60/pull/10) + - helper関数に二つNodeを渡してしまうやり方は考えつかなかったな。この問題に関しては、様々な書き方がありそう。 +[potrueさんのPR](https://github.com/potrue/leetcode/pull/7) + - 残されたコメントを見る限り、再帰的な解法では、せっかく問題を分解しているのだから、reverseしたlistの先頭と末尾を返して1つのステップ、直接隣り合ったNodeだけ操作する方法の方が自然...なのだろうか。 +[5ky7さんのPR](https://github.com/potrue/leetcode/pull/7) +[irohafternoonさんのPR](https://github.com/irohafternoon/LeetCode/pull/9) +[garunituleさんのPR](https://github.com/garunitule/coding_practice/pull/7) + +## 再帰的な解法 + +どの解法も納得感があるが、最近再帰関数でペアを返すことで解ける問題の幅が広いことに気づき、もう少し練習したいという思いから(また、現時点で言語化できない個人的な好みにより)、step 3はstep1のペアを返す方法で実装することにする。 + +### 別解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: + return None + if head.next is None: + return head + + reversed_head = self.reverseList(head.next) + head.next.next = head + head.next = None # for reversed tail + return reversed_head +``` + +### 別解2 + +```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]: + def reverse_list_helper( + prev_node: Optional[ListNode], curr_node: Optional[ListNode] + ) -> Optional[ListNode]: + if curr_node is None: + return prev_node + + reversed_head = reverse_list_helper(curr_node, curr_node.next) + curr_node.next = prev_node + return reversed_head + + return reverse_list_helper(None, head) +``` From 20ec01e3b72e2f95c0139dd73e72e0c834094a4e Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 12:35:41 -0700 Subject: [PATCH 03/20] LeetCode 206: Unify parentheses --- leetcode/206/memo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 58a3997..e4f7b35 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -12,7 +12,7 @@ ## 再帰的な解法 -どの解法も納得感があるが、最近再帰関数でペアを返すことで解ける問題の幅が広いことに気づき、もう少し練習したいという思いから(また、現時点で言語化できない個人的な好みにより)、step 3はstep1のペアを返す方法で実装することにする。 +どの解法も納得感があるが、最近再帰関数でペアを返すことで解ける問題の幅が広いことに気づき、もう少し練習したいという思いから(また、現時点で言語化できない個人的な好みにより)、step 3はstep1のペアを返す方法で実装することにする。 ### 別解1 From 272589a5d9d1b4e099ef92a39059874be57c08a8 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 13:14:12 -0700 Subject: [PATCH 04/20] LeetCode 206: Add step 2 iterative solution --- leetcode/206/memo.md | 2 ++ leetcode/206/step2_iterative.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 leetcode/206/step2_iterative.py diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index e4f7b35..de78081 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -1,5 +1,7 @@ # Step 2 +変数名がなかなか悩みどころ。 + ## 他の方々のPRを見る [h1rosakaさんのPR](https://github.com/h1rosaka/arai60/pull/10) diff --git a/leetcode/206/step2_iterative.py b/leetcode/206/step2_iterative.py new file mode 100644 index 0000000..4cf6e31 --- /dev/null +++ b/leetcode/206/step2_iterative.py @@ -0,0 +1,19 @@ +# 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]: + reversed_head = None + node = head + + while node: + next_node_to_reverse = node.next + + node.next = reversed_head + + reversed_head = node + node = next_node_to_reverse + + return reversed_head From b69e1705ca17c24093ae6251cc16b534aac42fcd Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 13:34:49 -0700 Subject: [PATCH 05/20] LeetCode 206: Add step 2 recursive solution --- leetcode/206/memo.md | 2 +- leetcode/206/step2_recursive.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 leetcode/206/step2_recursive.py diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index de78081..9ca188a 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -14,7 +14,7 @@ ## 再帰的な解法 -どの解法も納得感があるが、最近再帰関数でペアを返すことで解ける問題の幅が広いことに気づき、もう少し練習したいという思いから(また、現時点で言語化できない個人的な好みにより)、step 3はstep1のペアを返す方法で実装することにする。 +どの解法も納得感があるが、最近再帰関数でペアを返すことで解ける問題の幅が広いことに気づき、もう少し練習したいという思い、一つのステップでやる操作が直感的、(また、現時点で言語化できない個人的な好みにより)、step 3はstep1のペアを返す方法で実装することにする。 ### 別解1 diff --git a/leetcode/206/step2_recursive.py b/leetcode/206/step2_recursive.py new file mode 100644 index 0000000..46e306b --- /dev/null +++ b/leetcode/206/step2_recursive.py @@ -0,0 +1,24 @@ +# 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]: + def reverse_list_helper( + node: Optional[ListNode], + ) -> tuple[ + Optional[ListNode], Optional[ListNode] + ]: # (reversed_tail, reversed_head) + if node is None: + return None, None + if node.next is None: + return node, node + + reversed_tail, reversed_head = reverse_list_helper(node.next) + reversed_tail.next = node + node.next = None + + return node, reversed_head + + return reverse_list_helper(head)[1] From b1cd5eef2750ef093a6dc2def5897ee79b356d59 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 13:46:36 -0700 Subject: [PATCH 06/20] LeetCode 206: Add a note in step 2 --- leetcode/206/memo.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 9ca188a..42f787f 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -2,6 +2,8 @@ 変数名がなかなか悩みどころ。 +`prev`, `curr`, `next`だと、reverseしたlistにおける"前"や"次"なのか、元のlistにおける"前"や"次"なのか少し混乱する。 + ## 他の方々のPRを見る [h1rosakaさんのPR](https://github.com/h1rosaka/arai60/pull/10) From bf9db1554d18a8649a406d3d1d44778b92bea824 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 14:24:38 -0700 Subject: [PATCH 07/20] LeetCode 206: Add notes in step 2 --- leetcode/206/memo.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 42f787f..21f447f 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -4,6 +4,38 @@ `prev`, `curr`, `next`だと、reverseしたlistにおける"前"や"次"なのか、元のlistにおける"前"や"次"なのか少し混乱する。 +また、処理をどうグループ化するかも悩ましい。4つの処理をスペース無しで並べると、それらに順序的な関係がないような印象を覚えてしまうので (偏った感覚?)、スペースを入れたいのだが + +```python +while node: + # 次に処理するnodeを待避 + next_node_to_reverse = node.next + + # 繋ぎかえて reversed listを伸ばす + node.next = reversed_head + reversed_head = node + + # 処理するnodeを進める + node = next_node_to_reverse +``` + +と見るか + +```python +while node: + # 次に処理するnodeを待避 + next_node_to_reverse = node.next + + # 処理中のnodeのreferenceをひっくり返す + node.next = reversed_head + + # referenceの更新 + reversed_head = node + node = next_node_to_reverse +``` + +と見るかで少し悩んだ。 + ## 他の方々のPRを見る [h1rosakaさんのPR](https://github.com/h1rosaka/arai60/pull/10) From 08426691071f10594c7ba6325f39e9eb076eae63 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 14:25:22 -0700 Subject: [PATCH 08/20] LeetCode 206: Tweak step 2 iterative solution --- leetcode/206/step2_iterative.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leetcode/206/step2_iterative.py b/leetcode/206/step2_iterative.py index 4cf6e31..fae0e22 100644 --- a/leetcode/206/step2_iterative.py +++ b/leetcode/206/step2_iterative.py @@ -12,8 +12,8 @@ def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: next_node_to_reverse = node.next node.next = reversed_head - reversed_head = node + node = next_node_to_reverse return reversed_head From 2daa6f8d802426bc1de5a2075d70c27692cbc57d Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 14:27:22 -0700 Subject: [PATCH 09/20] LeetCode 206: Add step 3 iterative solution --- leetcode/206/step3_iterative.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 leetcode/206/step3_iterative.py diff --git a/leetcode/206/step3_iterative.py b/leetcode/206/step3_iterative.py new file mode 100644 index 0000000..fae0e22 --- /dev/null +++ b/leetcode/206/step3_iterative.py @@ -0,0 +1,19 @@ +# 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]: + reversed_head = None + node = head + + while node: + next_node_to_reverse = node.next + + node.next = reversed_head + reversed_head = node + + node = next_node_to_reverse + + return reversed_head From ef4cd95fa7d24aa184c1c898df85eb9b1a4f9181 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 14:31:24 -0700 Subject: [PATCH 10/20] LeetCode 206: Refine wording --- leetcode/206/memo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 21f447f..c197abc 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -4,7 +4,7 @@ `prev`, `curr`, `next`だと、reverseしたlistにおける"前"や"次"なのか、元のlistにおける"前"や"次"なのか少し混乱する。 -また、処理をどうグループ化するかも悩ましい。4つの処理をスペース無しで並べると、それらに順序的な関係がないような印象を覚えてしまうので (偏った感覚?)、スペースを入れたいのだが +また、処理をどうグループ化するかも悩ましい。while文の中に4つの短い処理をスペース無しで並べると、それらに順序的な関係がないような印象を覚えてしまうので (偏った感覚?)、スペースを入れたいのだが ```python while node: From a0b6426d63dc507f1b7c7ce65c9244fa397c42b6 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 15:57:55 -0700 Subject: [PATCH 11/20] LeetCode 206: Add step 3 recursive solution --- leetcode/206/step3_recursive.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 leetcode/206/step3_recursive.py diff --git a/leetcode/206/step3_recursive.py b/leetcode/206/step3_recursive.py new file mode 100644 index 0000000..46e306b --- /dev/null +++ b/leetcode/206/step3_recursive.py @@ -0,0 +1,24 @@ +# 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]: + def reverse_list_helper( + node: Optional[ListNode], + ) -> tuple[ + Optional[ListNode], Optional[ListNode] + ]: # (reversed_tail, reversed_head) + if node is None: + return None, None + if node.next is None: + return node, node + + reversed_tail, reversed_head = reverse_list_helper(node.next) + reversed_tail.next = node + node.next = None + + return node, reversed_head + + return reverse_list_helper(head)[1] From 723de26256908230cb85801e841efe5858b251b2 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 16:28:10 -0700 Subject: [PATCH 12/20] LeetCode 206: Mention Maslow's hammer --- leetcode/206/memo.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index c197abc..eed1d5d 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -29,7 +29,7 @@ while node: # 処理中のnodeのreferenceをひっくり返す node.next = reversed_head - # referenceの更新 + # 次に進むためのreferenceの更新 reversed_head = node node = next_node_to_reverse ``` @@ -41,14 +41,16 @@ while node: [h1rosakaさんのPR](https://github.com/h1rosaka/arai60/pull/10) - helper関数に二つNodeを渡してしまうやり方は考えつかなかったな。この問題に関しては、様々な書き方がありそう。 [potrueさんのPR](https://github.com/potrue/leetcode/pull/7) - - 残されたコメントを見る限り、再帰的な解法では、せっかく問題を分解しているのだから、reverseしたlistの先頭と末尾を返して1つのステップ、直接隣り合ったNodeだけ操作する方法の方が自然...なのだろうか。 + - 残されたコメントを見る限り、再帰的な解法では、せっかく問題を分解しているのだから、reverseしたlistの先頭と末尾を返して1つのステップを簡潔にする方法が自然...なのだろうか。 [5ky7さんのPR](https://github.com/potrue/leetcode/pull/7) [irohafternoonさんのPR](https://github.com/irohafternoon/LeetCode/pull/9) [garunituleさんのPR](https://github.com/garunitule/coding_practice/pull/7) ## 再帰的な解法 -どの解法も納得感があるが、最近再帰関数でペアを返すことで解ける問題の幅が広いことに気づき、もう少し練習したいという思い、一つのステップでやる操作が直感的、(また、現時点で言語化できない個人的な好みにより)、step 3はstep1のペアを返す方法で実装することにする。 +どの解法も納得感があるが、現時点で言語化できない個人的な好みにより、step3はstep1のペアを返す方法で実装することにする。 +- 最近、再帰関数でペアを返すことで解ける問題の幅が広いことに気づいた。 + - "If the only tool you have is a hammer, it is tempting to treat everything as if it were a nail." ### 別解1 From f2a6a87df30481b519fb60031365d830692b33bd Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 16:38:42 -0700 Subject: [PATCH 13/20] LeetCode 206: Refine wording --- leetcode/206/memo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index eed1d5d..acd4270 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -48,7 +48,7 @@ while node: ## 再帰的な解法 -どの解法も納得感があるが、現時点で言語化できない個人的な好みにより、step3はstep1のペアを返す方法で実装することにする。 +どの解法も納得感があるが、現時点で言語化できない個人的な好みにより、step3はstep1と同様にペアを返す方法で実装することにする。 - 最近、再帰関数でペアを返すことで解ける問題の幅が広いことに気づいた。 - "If the only tool you have is a hammer, it is tempting to treat everything as if it were a nail." From d1bcd02806c558f8ad12da89432c32e1122739bd Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 16:58:58 -0700 Subject: [PATCH 14/20] LeetCode 206: Replace a wrong URL with the right one --- leetcode/206/memo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index acd4270..6e6ead5 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -42,7 +42,7 @@ while node: - helper関数に二つNodeを渡してしまうやり方は考えつかなかったな。この問題に関しては、様々な書き方がありそう。 [potrueさんのPR](https://github.com/potrue/leetcode/pull/7) - 残されたコメントを見る限り、再帰的な解法では、せっかく問題を分解しているのだから、reverseしたlistの先頭と末尾を返して1つのステップを簡潔にする方法が自然...なのだろうか。 -[5ky7さんのPR](https://github.com/potrue/leetcode/pull/7) +[5ky7さんのPR](https://github.com/5ky7/arai60/pull/8) [irohafternoonさんのPR](https://github.com/irohafternoon/LeetCode/pull/9) [garunituleさんのPR](https://github.com/garunitule/coding_practice/pull/7) From 023457c48dbae5288ec9a40bb5e850f6f6ce8781 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 17:04:59 -0700 Subject: [PATCH 15/20] LeetCode 206: Add notes in step 3 --- leetcode/206/memo.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 6e6ead5..bb9610b 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -95,3 +95,7 @@ class Solution: return reverse_list_helper(None, head) ``` + +# Step 3 + +Step 3までやってようやくしっくりきたのだが、私のiterativeな解法は元のリストを先頭→末尾に進みながら繋ぎかえが発生するが、recursiveだと元のリストの末尾→先頭の順で繋ぎかえが発生している(Stackを用いているため逆順になるのは当たり前だが)。 From 6fdbad71cfe6894d8ee8ba4a3a16d1c64eed84f6 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Fri, 16 May 2025 17:12:23 -0700 Subject: [PATCH 16/20] LeetCode 206: Add notes in step 1 --- leetcode/206/memo.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index bb9610b..36dd4c8 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -1,3 +1,7 @@ +# Step 1 + +Linked List系の問題は、referenceの操作が正しくできるかどうかが測られているので、(dummy以外)新しいNodeを作らない解法が求められているのだろう、と予想する。 + # Step 2 変数名がなかなか悩みどころ。 From 51276fffb3867d21b5e58cd6ad974650797f60e0 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 21 May 2025 03:58:25 -0700 Subject: [PATCH 17/20] LeetCode 206: Add research on tail recursion (and tail call optimization) --- leetcode/206/memo.md | 83 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 36dd4c8..1799a54 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -103,3 +103,86 @@ class Solution: # Step 3 Step 3までやってようやくしっくりきたのだが、私のiterativeな解法は元のリストを先頭→末尾に進みながら繋ぎかえが発生するが、recursiveだと元のリストの末尾→先頭の順で繋ぎかえが発生している(Stackを用いているため逆順になるのは当たり前だが)。 + +# Step 4 + +## Tail Recursion + +末尾再帰 (tail recursion)とは、再帰のパターンの一種で、再帰関数において再起呼び出しが処理の末尾にあり、その戻り値をそのまま関数全体の戻り値として使用しているパターンを指す。 +末尾再帰の利点は、通常の再帰と異なり、再起呼び出しの後に追加の計算がないため、呼び出し側のスタックフレームを保持する必要がなくなる点にある。 +そうすると、ループ構造に機械的に書き換えられる、または末尾呼び出し最適化(tail call optimization)によってスタックフレームを積まずに済む。 + +```python +def reverse_the_first_and_append_the_second(reversing, appending): + if reversing is None: + return appending + reversing_tails = reversing.next + reversing.next = appending + return reverse_the_first_and_append_the_second(reversing_tails, reversing) +``` + +```python + def reverse_the_first_and_append_the_second(reversing, appending): + while reversing is not None: + reversing_tails = reversing.next + reversing.next = appending + reversing, appending = reversing_tails, reversing + return appending +``` + +### Resources + +[Tail recursion - HaskellWiki](https://wiki.haskell.org/index.php?title=Tail_recursion) + +> A recursive function is tail recursive if the final result of the recursive call is the final result of the function itself. If the result of the recursive call must be further processed (say, by adding 1 to it, or consing another element onto the beginning of it), it is not tail recursive. +> In many programming languages, calling a function uses stack space, so a function that is tail recursive can build up a large stack of calls to itself, which wastes memory. Since in a tail call, the containing function is about to return, its environment can actually be discarded and the recursive call can be entered without creating a new stack frame. This trick is called tail call elimination or tail call optimisation and allows tail-recursive functions to recur indefinitely. + +[3.1.1.5. Tail Recursion · Functional Programming in OCaml](https://courses.cs.cornell.edu/cs3110/2021sp/textbook/data/tail_recursion.html?q=) + +[github.com/ocaml - ocaml/stdlib/list.ml](https://github.com/ocaml/ocaml/blob/d325f299896417c5f1d477171135acfdf402e770/stdlib/list.ml#L57) + +```ocaml +let rec rev_append l1 l2 = + match l1 with + [] -> l2 + | a :: l -> rev_append l (a :: l2) + +let rev l = rev_append l [] +``` + +これはOdaさんにいただいた末尾再帰のReverse Linked Listのコードとほぼ一致する。 + +```python +def reverse_the_first_and_append_the_second(reversing, appending): + if reversing is None: + return appending + reversing_tails = reversing.next + reversing.next = appending + return reverse_the_first_and_append_the_second(reversing_tails, reversing) +``` + +### Examples + +階乗を求めるプログラム(末尾再帰ではない形)。この方法では、再起呼び出しの結果にnをかけている。 + +```python +def factorial(n: int) -> int: + if n == 0: + return 1 + return n * factorial(n - 1) +``` + +末尾再帰で書くと + +```python +def factorial(n: int, accumulated: int = 1) -> int: + if n == 0: + return accumulated + return factorial(n - 1, accumulated * n) +``` + +この方法では、関数内で最後にやることが再起呼び出しの結果を返すことになっている。 + +## Feedback + +TODO: Add feedback from reviewers here From e6d6b0e4a59eb4e93028b9957b1437113be6b834 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 21 May 2025 16:44:47 -0700 Subject: [PATCH 18/20] LeetCode 206: Add feedback --- leetcode/206/memo.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 1799a54..dec7b3c 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -185,4 +185,12 @@ def factorial(n: int, accumulated: int = 1) -> int: ## Feedback -TODO: Add feedback from reviewers here +- **再帰 <-> ループ間変形** + - 末尾再帰 +- 変数の末尾にコメントを書くとフォーマットが大変なので、素直にdocstringに書いた方がいいかも +- 意味ではなく操作の順番からの命名 (previous) に気を付ける、パズルを作らないように +- 四行くらいしかないなら空行を入れても読みやすさは変わらないかも + - どうせ空行を入れるなら、コメントを積極的に入れてもいいかも +- `reversing`, `appending`という命名 +- PEP8の (sparingly) というニュアンスに気づかなかった "Extra blank lines may be used (sparingly) to separate groups of related functions." +- `rest*`, `*_tails` From 5bb3f8c32ac7b3dcc350fb554231b86823770620 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Wed, 21 May 2025 16:46:18 -0700 Subject: [PATCH 19/20] LeetCode 206: Add TODO for step 4 --- leetcode/206/memo.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index dec7b3c..4ea0a52 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -194,3 +194,7 @@ def factorial(n: int, accumulated: int = 1) -> int: - `reversing`, `appending`という命名 - PEP8の (sparingly) というニュアンスに気づかなかった "Extra blank lines may be used (sparingly) to separate groups of related functions." - `rest*`, `*_tails` + +## TODO: + +- いただいたフィードバック(変数名・空行・再帰 <-> ループ)に気を遣いながらもう数回やり直してみる。 From e74c6a39bae5ed1cf39ed6b0980a3afbd4797e2f Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Thu, 22 May 2025 20:40:33 -0700 Subject: [PATCH 20/20] LeetCode 206: Retry --- leetcode/206/memo.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/leetcode/206/memo.md b/leetcode/206/memo.md index 4ea0a52..9d52296 100644 --- a/leetcode/206/memo.md +++ b/leetcode/206/memo.md @@ -191,10 +191,45 @@ def factorial(n: int, accumulated: int = 1) -> int: - 意味ではなく操作の順番からの命名 (previous) に気を付ける、パズルを作らないように - 四行くらいしかないなら空行を入れても読みやすさは変わらないかも - どうせ空行を入れるなら、コメントを積極的に入れてもいいかも + - もう一度書いてみると、確かに空行なくてもいいかな、と思った。仕事だと、長いscript的なものを書くと空行でブロックを区切るが、そこまでの量でもない。 + - 私の脳内回路がまだ組み変わっていないのか、書き下すとまだ少し違和感が残るが、少し時間をおいてみよう。 - `reversing`, `appending`という命名 - PEP8の (sparingly) というニュアンスに気づかなかった "Extra blank lines may be used (sparingly) to separate groups of related functions." - `rest*`, `*_tails` -## TODO: +```python +# Step 4 tail recursion +# 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]: + def reverse_append(reversing, appending): + if reversing is None: + return appending + reversing_tails = reversing.next + reversing.next = appending + return reverse_append(reversing_tails, reversing) + + return reverse_append(head, None) +``` -- いただいたフィードバック(変数名・空行・再帰 <-> ループ)に気を遣いながらもう数回やり直してみる。 +```python +# Step 4 tail recursion -> iterative +# 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]: + appending = None + reversing = head + while reversing: + reversing_tails = reversing.next + reversing.next = appending + reversing, appending = reversing_tails, reversing + return appending +```