From ed157f3677861daeb19638567010b95958bc3e45 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sat, 28 Jun 2025 00:20:08 -0700 Subject: [PATCH 1/8] LeetCode 53: Add a solution during the first attempt --- leetcode/53/memo.md | 7 +++++++ leetcode/53/step1_prefix_sum_array_and_greedy.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 leetcode/53/memo.md create mode 100644 leetcode/53/step1_prefix_sum_array_and_greedy.py diff --git a/leetcode/53/memo.md b/leetcode/53/memo.md new file mode 100644 index 0000000..834f4ae --- /dev/null +++ b/leetcode/53/memo.md @@ -0,0 +1,7 @@ +# step 1 + +## Prefix Sum Array + Greedy + +SubarrayのsumといえばPrefix Sum Array。一度Prefix Sum Arrayを作ったら、あとは[LeetCode 121. Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)と同じ問題だな、と思った。 + +時間計算量: O(n)、空間計算量: O(n) diff --git a/leetcode/53/step1_prefix_sum_array_and_greedy.py b/leetcode/53/step1_prefix_sum_array_and_greedy.py new file mode 100644 index 0000000..4bb9185 --- /dev/null +++ b/leetcode/53/step1_prefix_sum_array_and_greedy.py @@ -0,0 +1,15 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + prefix_sum_array = [0] + for num in nums: + prefix_sum_array.append(prefix_sum_array[-1] + num) + + max_subarray_sum = max(nums) + min_prefix_sum_so_far = float("inf") + for prefix_sum in prefix_sum_array: + if prefix_sum < min_prefix_sum_so_far: + min_prefix_sum_so_far = prefix_sum + continue + max_subarray_sum = max(max_subarray_sum, prefix_sum - min_prefix_sum_so_far) + + return max_subarray_sum From 24c509f136beef0dcabb15d2ffa1c95c9d4e7908 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sat, 28 Jun 2025 16:09:25 -0700 Subject: [PATCH 2/8] LeetCode 53: Add observations (step 1) --- leetcode/53/memo.md | 5 +++++ .../53/{step1_prefix_sum_array_and_greedy.py => step1.py} | 0 2 files changed, 5 insertions(+) rename leetcode/53/{step1_prefix_sum_array_and_greedy.py => step1.py} (100%) diff --git a/leetcode/53/memo.md b/leetcode/53/memo.md index 834f4ae..e73867c 100644 --- a/leetcode/53/memo.md +++ b/leetcode/53/memo.md @@ -5,3 +5,8 @@ SubarrayのsumといえばPrefix Sum Array。一度Prefix Sum Arrayを作ったら、あとは[LeetCode 121. Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)と同じ問題だな、と思った。 時間計算量: O(n)、空間計算量: O(n) + +## 他の解法 + +Follow upでdivide and conquerが仄めかされていたので、どうにか問題を分割できないか考えていたのだが、思いつかなかった。 +全体から見ると最大になるSubarrayも、部分問題で見たら最大になるとは限らないところに難しさがあるように思った。 diff --git a/leetcode/53/step1_prefix_sum_array_and_greedy.py b/leetcode/53/step1.py similarity index 100% rename from leetcode/53/step1_prefix_sum_array_and_greedy.py rename to leetcode/53/step1.py From d554b083e70f1d4ca5cc51eb254d1e69b81ebc3a Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sun, 29 Jun 2025 13:09:44 -0700 Subject: [PATCH 3/8] LeetCode 53: Reviewed potrue's pull reuqest --- leetcode/53/memo.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/leetcode/53/memo.md b/leetcode/53/memo.md index e73867c..6179578 100644 --- a/leetcode/53/memo.md +++ b/leetcode/53/memo.md @@ -1,4 +1,4 @@ -# step 1 +# Step 1 ## Prefix Sum Array + Greedy @@ -10,3 +10,28 @@ SubarrayのsumといえばPrefix Sum Array。一度Prefix Sum Arrayを作った Follow upでdivide and conquerが仄めかされていたので、どうにか問題を分割できないか考えていたのだが、思いつかなかった。 全体から見ると最大になるSubarrayも、部分問題で見たら最大になるとは限らないところに難しさがあるように思った。 + +# Step 2 + +## 他の方々のPR + +### [potrueさんのPR](https://github.com/potrue/leetcode/pull/32) + +私と同じ方針だが、prefix sum arrayを先に作らずに、ループ内で累積和を更新していけば、空間計算量 O(1) で済んでいる。 + +```python +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + cumulative_sum = 0 + min_cumulative_sum_so_far = 0 + max_subarray_sum = -float("inf") + + for num in nums: + cumulative_sum += num + max_subarray_sum = max( + max_subarray_sum, cumulative_sum - min_cumulative_sum_so_far + ) + min_cumulative_sum_so_far = min(cumulative_sum, min_cumulative_sum_so_far) + + return max_subarray_sum +``` From d25242d3b6d26288e14fc417c76f01ae6138dc32 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sun, 29 Jun 2025 19:45:34 -0700 Subject: [PATCH 4/8] LeetCode 53: Reviewed pull requests --- leetcode/53/memo.md | 59 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/leetcode/53/memo.md b/leetcode/53/memo.md index 6179578..0e295f8 100644 --- a/leetcode/53/memo.md +++ b/leetcode/53/memo.md @@ -15,7 +15,7 @@ Follow upでdivide and conquerが仄めかされていたので、どうにか ## 他の方々のPR -### [potrueさんのPR](https://github.com/potrue/leetcode/pull/32) +- [potrueさんのPR](https://github.com/potrue/leetcode/pull/32) 私と同じ方針だが、prefix sum arrayを先に作らずに、ループ内で累積和を更新していけば、空間計算量 O(1) で済んでいる。 @@ -35,3 +35,60 @@ class Solution: return max_subarray_sum ``` + +- [SatorienさんのPR](https://github.com/Satorien/LeetCode/pull/32) + +Divide and conquerの解法について言及されていた。しばらく眺めてもよくわからなかったので、他の方々のPRも先に見てみることにする。 + +- [ryooooooryさんのPR](https://github.com/ryoooooory/LeetCode/pull/35) + +丁寧に (brute-force ->) DP -> 部分和 という思考ステップが記録されていた。累積和に飛びついた私よりも、こちらの思考ステップの方が納得感がある。 + +- [Kazuryu0907さんのPR](https://github.com/Kazuryu0907/LeetCode_Arai60/pull/5) + +動的計画法。f(i)をiで終わるsubarrayの最大値だとすると、f(i) = max(f(i - 1) + nums[i], nums[i])。 +なるほど、こういう見方をすれば再帰的な関係が置けて、動的計画法ができるのか。 + +空間計算量を削らず、Kazuryu0907さんの考え方を自分なりに解釈すると以下のようになるだろうか。 + +```python +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + # max_ending_subarray_sum[i] -> i で終わるsubarrayの最大値 + max_ending_subarray_sums: list[int] = [0] * (len(nums) + 1) + max_ending_subarray_sums[0] = float("-inf") + + for i, num in enumerate(nums): + max_ending_subarray_sums[i + 1] = max( + max_ending_subarray_sums[i] + num, num + ) + + return max(max_ending_subarray_sums) +``` + +- [tokuhiratさんのPR](https://github.com/tokuhirat/LeetCode/pull/32) + +私とほぼ同じstep 1の発想とstep 3にある最終的なコード。 + +## Kadane's Algorithm + +tokuhiratさんのPRでも言及されていたが、別の問題のPRをレビューしている時に Kadane's Algorithmというものに遭遇した記憶があった。 +上の動的計画法のコードから空間計算量を削ったコードで、あるインデックスで終わる部分和の最大値、というところが発想のキモであるように感じた。 +長さ0のsubarrayを許容する問題設定の場合、部分和 or 今指している値、の後者を0にすればい良い。 + +一般常識には含まれないらしい。 + +```python +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + max_subarray_sum = -float("inf") + ending_subarray_sum = 0 + for num in nums: + ending_subarray_sum = max(ending_subarray_sum + num, num) + max_subarray_sum = max(max_subarray_sum, ending_subarray_sum) + return max_subarray_sum +``` + +## その他 - Divide and Conquer + +LeetCodeのSolutionsをざっと眺めて、一晩寝てもよくわからなかった。LeetCodeのSolutions上に残してあるコメントや、一社内のPRを見て予想するに、多くの人は分割統治法を思いつけない・書けないし、現在の私の理解力を超えているように思うので、一旦スキップすることにする。後で戻ってきた時にわかるようになっているかも。 From faf37c4f1c42d59c433f21f401a0990888669392 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sun, 29 Jun 2025 23:06:04 -0700 Subject: [PATCH 5/8] LeetCode 53: Add step 2 solutions --- leetcode/53/memo.md | 15 +++++++++++---- leetcode/53/step2_kadane.py | 8 ++++++++ leetcode/53/step2_prefix_sum.py | 10 ++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 leetcode/53/step2_kadane.py create mode 100644 leetcode/53/step2_prefix_sum.py diff --git a/leetcode/53/memo.md b/leetcode/53/memo.md index 0e295f8..4e44680 100644 --- a/leetcode/53/memo.md +++ b/leetcode/53/memo.md @@ -82,13 +82,20 @@ tokuhiratさんのPRでも言及されていたが、別の問題のPRをレビ class Solution: def maxSubArray(self, nums: list[int]) -> int: max_subarray_sum = -float("inf") - ending_subarray_sum = 0 + max_ending_subarray_sum = 0 for num in nums: - ending_subarray_sum = max(ending_subarray_sum + num, num) - max_subarray_sum = max(max_subarray_sum, ending_subarray_sum) + max_ending_subarray_sum = max(max_ending_subarray_sum + num, num) + max_subarray_sum = max(max_subarray_sum, max_ending_subarray_sum) return max_subarray_sum ``` -## その他 - Divide and Conquer +## その他 + +### Divide and Conquer LeetCodeのSolutionsをざっと眺めて、一晩寝てもよくわからなかった。LeetCodeのSolutions上に残してあるコメントや、一社内のPRを見て予想するに、多くの人は分割統治法を思いつけない・書けないし、現在の私の理解力を超えているように思うので、一旦スキップすることにする。後で戻ってきた時にわかるようになっているかも。 + +### 変数名 + +Kadane's Algorithmの解法の変数に対して、端的で明確な名付けができなかった。変数名がやけに長くなってもしょうがないし、ある程度短いものにして、あとは処理から推測してもらう方がいいだろうか。 + diff --git a/leetcode/53/step2_kadane.py b/leetcode/53/step2_kadane.py new file mode 100644 index 0000000..9f40209 --- /dev/null +++ b/leetcode/53/step2_kadane.py @@ -0,0 +1,8 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + max_ending = 0 + max_sum = float("-inf") + for num in nums: + max_ending = max(max_ending + num, num) + max_sum = max(max_sum, max_ending) + return max_sum diff --git a/leetcode/53/step2_prefix_sum.py b/leetcode/53/step2_prefix_sum.py new file mode 100644 index 0000000..30ecbee --- /dev/null +++ b/leetcode/53/step2_prefix_sum.py @@ -0,0 +1,10 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + prefix_sum = 0 + min_prefix_sum = 0 + max_subarray_sum = float("-inf") + for num in nums: + prefix_sum += num + max_subarray_sum = max(max_subarray_sum, prefix_sum - min_prefix_sum) + min_prefix_sum = min(min_prefix_sum, prefix_sum) + return max_subarray_sum From 1f1e815c9fad51105a98776346a57b8e2a85292a Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sun, 29 Jun 2025 23:59:09 -0700 Subject: [PATCH 6/8] LeetCode 53: Add step 3 solutions --- leetcode/53/step3_kadane.py | 8 ++++++++ leetcode/53/step3_prefix_sum.py | 10 ++++++++++ 2 files changed, 18 insertions(+) create mode 100644 leetcode/53/step3_kadane.py create mode 100644 leetcode/53/step3_prefix_sum.py diff --git a/leetcode/53/step3_kadane.py b/leetcode/53/step3_kadane.py new file mode 100644 index 0000000..8098a75 --- /dev/null +++ b/leetcode/53/step3_kadane.py @@ -0,0 +1,8 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + max_ending_sum = float("-inf") + max_sum = float("-inf") + for num in nums: + max_ending_sum = max(max_ending_sum + num, num) + max_sum = max(max_sum, max_ending_sum) + return max_sum diff --git a/leetcode/53/step3_prefix_sum.py b/leetcode/53/step3_prefix_sum.py new file mode 100644 index 0000000..30ecbee --- /dev/null +++ b/leetcode/53/step3_prefix_sum.py @@ -0,0 +1,10 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + prefix_sum = 0 + min_prefix_sum = 0 + max_subarray_sum = float("-inf") + for num in nums: + prefix_sum += num + max_subarray_sum = max(max_subarray_sum, prefix_sum - min_prefix_sum) + min_prefix_sum = min(min_prefix_sum, prefix_sum) + return max_subarray_sum From 05b4b4d8691489d339f8529093f93aa818d46a59 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Mon, 30 Jun 2025 00:02:18 -0700 Subject: [PATCH 7/8] LeetCode 53: Add findings during step 3 --- leetcode/53/memo.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/leetcode/53/memo.md b/leetcode/53/memo.md index 4e44680..c70af51 100644 --- a/leetcode/53/memo.md +++ b/leetcode/53/memo.md @@ -99,3 +99,7 @@ LeetCodeのSolutionsをざっと眺めて、一晩寝てもよくわからなか Kadane's Algorithmの解法の変数に対して、端的で明確な名付けができなかった。変数名がやけに長くなってもしょうがないし、ある程度短いものにして、あとは処理から推測してもらう方がいいだろうか。 +# Step 3 + +初期値が`0`でも`float("-inf")`でもいい時(`max_ending_sum`)、どちらを採用しようか迷う。 +また、先にDPの解法があってから空間計算量を削ってKadane's Algorithm、という流れを欠いて、Kadane's Algorithmだけを一発目で見ると理解に時間がかかるような気がする。 From dd0bfbbac016f6f97ce3efe90e3e46ad8284105f Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Mon, 30 Jun 2025 00:13:06 -0700 Subject: [PATCH 8/8] LeetCode 53: Tweak a solution --- leetcode/53/step3_kadane.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leetcode/53/step3_kadane.py b/leetcode/53/step3_kadane.py index 8098a75..f113be6 100644 --- a/leetcode/53/step3_kadane.py +++ b/leetcode/53/step3_kadane.py @@ -1,6 +1,6 @@ class Solution: def maxSubArray(self, nums: list[int]) -> int: - max_ending_sum = float("-inf") + max_ending_sum = 0 max_sum = float("-inf") for num in nums: max_ending_sum = max(max_ending_sum + num, num)