From 2a6bffaadafe2fd3ba169496304dc7120f322a1b Mon Sep 17 00:00:00 2001 From: nanae Date: Tue, 7 Oct 2025 12:43:57 +0900 Subject: [PATCH] add solutions --- 53-maximum-subarray/memo.md | 76 +++++++++++++++++++++ 53-maximum-subarray/step1.py | 12 ++++ 53-maximum-subarray/step2-divide-conquer.py | 23 +++++++ 53-maximum-subarray/step2-kadane.py | 9 +++ 53-maximum-subarray/step3.py | 11 +++ 5 files changed, 131 insertions(+) create mode 100644 53-maximum-subarray/memo.md create mode 100644 53-maximum-subarray/step1.py create mode 100644 53-maximum-subarray/step2-divide-conquer.py create mode 100644 53-maximum-subarray/step2-kadane.py create mode 100644 53-maximum-subarray/step3.py diff --git a/53-maximum-subarray/memo.md b/53-maximum-subarray/memo.md new file mode 100644 index 0000000..13936b2 --- /dev/null +++ b/53-maximum-subarray/memo.md @@ -0,0 +1,76 @@ +# ステップ1 + +和が最大になるsubarray(連続した部分列)を求める + +全て非負なら全部取ればいいだけだが、負の数があるのでそう簡単にはいかない。 + +全てのsubarrayを試す全探索はO(N^2)かかるのでN=10^5の最大ケースでは厳しい。 + +非負が続く連続する区間はまとめて取ってもよく、また負が連続する区間も +取ると決めたなら途中で辞める意味は無さそうなのでこれもまとめると + +[正、負、正、…] +または +[負、正、負、…] + +と符号が交互に入れ替わる配列にしてもよさそうであるが、これをしたところでどうなるかはよく分かってない。 + +累積和の考え方を使ってみる。s[i] = (a[0]..a[i-1]までの総和)とすると +rを固定したときに`[l, r)`の和はs[r] - s[l]なので、`l < r`でs[l]が最小になるものを求めればいいということである。 +これは適当な変数で今まで見たs[i]の最小を保存しておけばO(1)で求められるので、全体としてO(N)で解けることになる。 + +subarrayは空でもいいか確認し、subarrayの条件はnon-emptyであると問題文に書いてあった。 +ということは全部マイナスの場合は負にもなりえる。 + +とりあえず解けた。 + +follow-upでdivide and conquer、分割統治を使う解を考えてみようと書かれているがぱっと分からない。 +区間を半分に分けて、左と右でmax subarrayを求めてそれらをくっつける? +左で求めた区間と右で求めた区間を間を伸ばして足してより大きな和ができるならそれを解とする、 +そうでないなら左と右で比較してより大きい和を持つ方を解とする…みたいなことなのか? +本当にそれでいけるんだろうか…、ちょっと自信が無い。いや、なんか違うな。 +こっちはちょっと10分くらい考えても分からないので次のステップで他の人のコードを見て学ぶことにする。 + +# ステップ2 + +https://github.com/garunitule/coding_practice/pull/32/files#diff-5773de699c600fdf54940c38b7799b23dd0c335acf25ddce1b93c7335404131aR65-R68 + +分割統治の解法。midを取って、(左半分), mid, (右半分)に分けて、 + +1. 左半分の最大(再帰で求める) +2. 右半分の最大(再帰で求める) +3. midから始めて左にどこまで伸ばせるか、右にどこまで伸ばせるかをループを回して計算 + +で3つの区間の中から最大を取るのか。これなら確かに上手くいきそう? +計算量はステップ3で区間分のループを回すので +`T(N) = cN + 2*T(N/2) = cN + cN + 4*T(N/4) = ...` +という漸化式になり、これはO(NlogN)になるかな。 + +https://github.com/Fuminiton/LeetCode/pull/32#discussion_r2049052995 + +整数の最大値としてsys.maxsizeを使っている人もいるが実際はPythonは多倍長整数なのでいくらでも小さくできるという話。 +そもそも今回は適当にnums[0]を初期値とするなどのほうがスマートかもしれない。 + +https://en.wikipedia.org/wiki/Maximum_subarray_problem#Kadane's_algorithm + +この問題に対してはKadane's algorithmという名前がついた解き方があるらしい。 +計算量は自分が書いたものと同じだが、current_sumにi(0 <= i < j)からj-1までの部分和の最大を持っておくという発想。 +current_sumは + +- 今まで持ってるcurrent_sumにnums[j]をつけて延長する +- 今までの分を捨てて新たにnums[j]から始める + +のどちらか大きいほうだけを覚えていればいい。 + +Kadaneは自然なdpからやれば出てくる気がした。dp[i] = (iで終わる連続部分配列の最大和)とすれば + +- dp[i] = max(dp[i - 1] + nums[i], nums[i]) +- 最終的な答えはmax(dp) + +と考えられて、よく見るとdp[i]を計算するときにdp[i-1]しか使ってないので適当な変数にすると +Kadane's algorithmになる。 + +# ステップ3 + +最終的にKadaneのほうがしっくり来たのでそちらで。 +累積和のほうはmax, minが入り交ざったり引き算が必要だったりするのでちょっと思考負荷が高い気がした。 diff --git a/53-maximum-subarray/step1.py b/53-maximum-subarray/step1.py new file mode 100644 index 0000000..92fa834 --- /dev/null +++ b/53-maximum-subarray/step1.py @@ -0,0 +1,12 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + prefix_sum = 0 + min_prefix_sum = 0 + max_sum_subarray = -float("inf") + + for num in nums: + prefix_sum += num + max_sum_subarray = max(max_sum_subarray, prefix_sum - min_prefix_sum) + min_prefix_sum = min(min_prefix_sum, prefix_sum) + + return max_sum_subarray diff --git a/53-maximum-subarray/step2-divide-conquer.py b/53-maximum-subarray/step2-divide-conquer.py new file mode 100644 index 0000000..aa5d096 --- /dev/null +++ b/53-maximum-subarray/step2-divide-conquer.py @@ -0,0 +1,23 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + def calculate_max_subarray(left: int, right: int) -> int | float: + if left == right: + return -float("inf") + + mid = (left + right) // 2 + max_sum_left = calculate_max_subarray(left, mid) + max_sum_right = calculate_max_subarray(mid + 1, right) + + max_sum_includes_mid = nums[mid] + sum_ = nums[mid] + for i in range(mid + 1, right): + sum_ += nums[i] + max_sum_includes_mid = max(max_sum_includes_mid, sum_) + sum_ = max_sum_includes_mid + for i in reversed(range(left, mid)): + sum_ += nums[i] + max_sum_includes_mid = max(max_sum_includes_mid, sum_) + + return max(max_sum_includes_mid, max_sum_left, max_sum_right) + + return calculate_max_subarray(0, len(nums)) diff --git a/53-maximum-subarray/step2-kadane.py b/53-maximum-subarray/step2-kadane.py new file mode 100644 index 0000000..b97514f --- /dev/null +++ b/53-maximum-subarray/step2-kadane.py @@ -0,0 +1,9 @@ +# Kadane's algorithm +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + sum_ = nums[0] + max_sum = sum_ + for num in nums[1:]: + sum_ = max(sum_ + num, num) + max_sum = max(max_sum, sum_) + return max_sum diff --git a/53-maximum-subarray/step3.py b/53-maximum-subarray/step3.py new file mode 100644 index 0000000..7ce9129 --- /dev/null +++ b/53-maximum-subarray/step3.py @@ -0,0 +1,11 @@ +class Solution: + def maxSubArray(self, nums: list[int]) -> int: + if not nums: + return 0 + + max_subarray_sum = nums[0] + subarray_sum = 0 + for num in nums: + subarray_sum = max(subarray_sum + num, num) + max_subarray_sum = max(max_subarray_sum, subarray_sum) + return max_subarray_sum