-
Notifications
You must be signed in to change notification settings - Fork 0
53. Maximum Subarray #31
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,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が入り交ざったり引き算が必要だったりするのでちょっと思考負荷が高い気がした。 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
class Solution: | ||
def maxSubArray(self, nums: list[int]) -> int: | ||
if not nums: | ||
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. 空の入力に対して0を返されていますが、後ろのロジックでは空の入力 -> 0 を最大値として許容しておらず、少しだけ統一感がないような気がしました。 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. 確かに空配列の入力に対してどうするかはもう少しちゃんと考えるべきでした。 今回の要件としてsubarrayはnon-emptyであると問題で要請されているので、空配列はsubarrayを持たないため計算できないものとして例外を投げるのがよかったかなと自分でも思います。 コメントいただきありがとうございます。 |
||
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 |
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.
そうですね。
prefix_sum - min_prefix_sum を整理してもこれになります。