-
Notifications
You must be signed in to change notification settings - Fork 0
Solved: 53. Maximum Subarray #32
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,175 @@ | ||
| ## 取り組み方 | ||
| - step1: 5分以内に空で書いてAcceptedされるまで解く + テストケースと関連する知識を連想してみる | ||
| - step2: 他の方の記録を読んで連想すべき知識や実装を把握した上で、前提を置いた状態で最適な手法を選択し実装する | ||
| - step3: 10分以内に1回もエラーを出さずに3回連続で解く | ||
|
|
||
| ## step1 | ||
| nums[i]からnums[j]までの和を全てみて一番大きいものを返せば良さそう。 | ||
| 累積和を用いれば、i~jまでの和がO(1)でアクセスできるので、全体としてはO(nums.length^2)で解ける。 | ||
| が、10^5*10^5で10^10となるので、計算に数時間くらいかかってしまいそう。 | ||
|
|
||
| ので、効率化できないかを考える。 | ||
| 求めたいのは、prefix_sum[j+1]-prefix_sum[i]をi,j(i<=j)を動かしていった際の最大の値。 | ||
| jを固定して考えると、prefix_sum[i]が最小の時がprefix_sum[j+1]-prefix_sum[i]が最大となる。 | ||
| したがって、jを0~len(nums)-1まで動かしていって、 | ||
| その際の累積和とこれまでの累積和の最小値の差をmaximum subarrayとして更新していけば、O(nums.length)で解ける。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def maxSubArray(self, nums: List[int]) -> int: | ||
| maximum_subarray = -sys.maxsize - 1 | ||
| sum_prefix = 0 | ||
| minimum_sum_prefix = 0 | ||
| for num in nums: | ||
| sum_prefix += num | ||
| maximum_subarray = max( | ||
| maximum_subarray, | ||
| sum_prefix - minimum_sum_prefix | ||
| ) | ||
| minimum_sum_prefix = min( | ||
| minimum_sum_prefix, | ||
| sum_prefix | ||
| ) | ||
| return maximum_subarray | ||
| ``` | ||
|
|
||
| ## step2 | ||
| ### 読んだコード | ||
| - https://github.com/TORUS0818/leetcode/pull/34/files | ||
| - https://github.com/hayashi-ay/leetcode/pull/36/files | ||
| - https://github.com/sakupan102/arai60-practice/pull/33/files | ||
|
|
||
| ### 感想 | ||
| - subarrayの要素の和の最大を出したいので、変数名が不適切だった。なぜ気づかなかった、 | ||
| - wikipediaに取り上げられるくらい有名な問題で、別解も色々あった | ||
| - https://en.wikipedia.org/wiki/Maximum_subarray_problem | ||
| - kadaneのアルゴリズム | ||
| - 分割統治 | ||
| - 総当たり(累積和なし) | ||
| - 総当たり(累積和あり) <- 初見時、提出してTLEになった | ||
| - 分割統治は、並列処理が適切な場面では選択肢になるのだろうか | ||
| - kadaneは変数が減らせるくらいが嬉しさだろうと思うので、step1の実装の方がわかりやすくいい気がする | ||
|
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. 空間計算量がO(1)にできるのは入力が大きい場合はメリットだと思います。 |
||
|
|
||
| ##### kadaneのアルゴリズム | ||
| step1の実装の`maximum_subarray`の更新部分`sum_prefix - minimum_sum_prefix`を式変形していくと、 | ||
| `-> sum_prefix - min(prev_minimum_sum_prefix, prev_sum_prefix)` | ||
| `-> num + prev_sum_prefix - min(prev_minimum_sum_prefix, prev_sum_prefix)` | ||
| `-min(A, B) = max(-A, -B)`であることから、 | ||
| `-> num + max(prev_sum_prefix - prev_minimum_sum_prefix, prev_sum_prefix - prev_sum_prefix)` | ||
| `-> num + max(prev_sum_prefix - prev_minimum_sum_prefix, 0)` | ||
| `num`を共通因子としてmaxの中に入れて、 | ||
| `-> max(num, num + prev_sum_prefix - prev_minimum_sum_prefix)` | ||
| ここで、`prev_sum_prefix - prev_minimum_sum_prefix`は、 | ||
| `sum_prefix - minimum_sum_prefix`の1つ前の結果だったということになるので、 | ||
| `sum_prefix - minimum_sum_prefix`を`current_max_sum`とでもおいて、 | ||
| `current_max_sum = max(num, num + current_max_sum)`を更新していけば良いという話。 | ||
|
Comment on lines
+54
to
+65
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. 一言で言うと、こういうことですね。 |
||
|
|
||
| ```python | ||
| class Solution: | ||
| def maxSubArray(self, nums: List[int]) -> int: | ||
| max_sum = -sys.maxsize - 1 | ||
| current_max_sum = -sys.maxsize - 1 | ||
|
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. _so_far と最後につけて、「ここまでの」を意味する変数名にする方もいらっしゃるようです。
Owner
Author
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. 共有ありがとうございます。 |
||
| for num in nums: | ||
| current_max_sum = max(num, num + current_max_sum) | ||
| max_sum = max(max_sum, current_max_sum) | ||
| return max_sum | ||
| ``` | ||
|
|
||
| ##### 分割統治 | ||
| 配列を半分にして、1.左半分と2.右半分, 3.分割する場所を跨ぐ部分配列の中で和が最大になるものを探すという発想。 | ||
| 1.2.は再帰的に出せば良い。 | ||
| 3.は真ん中から左端に向かって最大値を記録していく + 真ん中+1から右端に向かって最大値を記録していき、2つの和を出す。 | ||
|
|
||
| 再帰の深さは`logN`で3.の処理が`N`なので、O(NlogN)で解ける。 | ||
|
|
||
|
|
||
| ```python | ||
| class Solution: | ||
| def maxSubArray(self, nums: List[int]) -> int: | ||
| def get_max_sum(left: int, right: int) -> int: | ||
| if left == right: | ||
| return nums[left] | ||
| center = (left + right) // 2 | ||
| left_max_sum = get_max_sum(left, center) | ||
| right_max_sum = get_max_sum(center + 1, right) | ||
| center_max_sum = get_center_max_sum(left, right) | ||
| return max( | ||
| left_max_sum, | ||
| right_max_sum, | ||
| center_max_sum | ||
| ) | ||
|
|
||
| def get_center_max_sum(left: int, right: int) -> int: | ||
| if left == right: | ||
| return nums[left] | ||
| center = (left + right) // 2 | ||
|
|
||
| left_max_sum = -sys.maxsize | ||
| left_direction_prefix_sum = 0 | ||
| for i in range(center, left - 1, -1): | ||
| left_direction_prefix_sum += nums[i] | ||
| left_max_sum = max( | ||
| left_max_sum, | ||
| left_direction_prefix_sum | ||
| ) | ||
| right_max_sum = -sys.maxsize | ||
| right_direction_prefix_sum = 0 | ||
| for i in range(center + 1, right + 1): | ||
| right_direction_prefix_sum += nums[i] | ||
| right_max_sum = max( | ||
| right_max_sum, | ||
| right_direction_prefix_sum | ||
| ) | ||
| return left_max_sum + right_max_sum | ||
|
|
||
| return get_max_sum(0, len(nums) - 1) | ||
| ``` | ||
|
|
||
| ## step3 | ||
| 分割統治も応用が効きそうなので、3回解いて定着させておく。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def maxSubArray(self, nums: List[int]) -> int: | ||
| max_subarray_sum = -sys.maxsize - 1 | ||
| prefix_sum = 0 | ||
| min_prefix_sum = 0 | ||
| 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 | ||
| ``` | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def maxSubArray(self, nums: List[int]) -> int: | ||
| def get_max_sum(left: int, right: int) -> int: | ||
| if left == right - 1: | ||
| return nums[left] | ||
| center = (left + right) // 2 | ||
| left_max_sum = get_max_sum(left, center) | ||
| right_max_sum = get_max_sum(center, right) | ||
| center_max_sum = get_max_center_sum(left, right) | ||
| return max(left_max_sum, center_max_sum, right_max_sum) | ||
|
|
||
| def get_max_center_sum(left: int, right: int) -> int: | ||
| center = (left + right) // 2 | ||
| left_max_sum = right_max_sum = -sys.maxsize - 1 | ||
| left_prefix_sum = right_prefix_sum = 0 | ||
| for i in range(center - 1, left - 1, -1): | ||
| left_prefix_sum += nums[i] | ||
| left_max_sum = max(left_max_sum, left_prefix_sum) | ||
| for i in range(center, right): | ||
| right_prefix_sum += nums[i] | ||
| right_max_sum = max(right_max_sum, right_prefix_sum) | ||
| return left_max_sum + right_max_sum | ||
|
|
||
| return get_max_sum(0, len(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. ただの感想なんですが、この問題の分割統治あまりきれいじゃない感じがしてしまいます。
Owner
Author
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. ご意見ありがとうございます。 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. 左右の分割 + O(n) は、たとえば、最大の内点を含まない長方形などでたまに出てくる気がします。今回はどんぴしゃという感じはしないですね。
Owner
Author
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. 面白い類題の共有ありがとうございます。 真ん中で平面を分割して左右で部分問題を解き、 確かにこのケースだと、左右の分割 + 中央を考慮した処理を使うのが自然な気がしました。 |
||
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.
-sys.maxsize - 1はintの最小値ではなさそうです。
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.
IronPythonにはこんな説明がありました。
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.
レビュー、実験、リンクの共有もいただきありがとうございます。
すると、
maximum_subarrayの初期値は、MIN_NUMS = -10001あたりを定義しておいて使うのが良さそうですかね。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.
あー、そもそもの話として、maximum_subarray に負の小さい値を入れるというのが「番兵」であるという認識はありますか。つまり、
を番兵使って、value の初期値を -inf などとしても状況によっては動くということです。
それで、入力への制約というのはだんだん守られなくなっていくものなので、番兵を使ってうまく書けないならば、使わないことも考えたほうがいいでしょう。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.jdtk9v35bca4
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.
レビューありがとうございます。
はじめのループで更新される、maximum_subarrayとしては現状あり得ないだろう値くらいの認識で、番兵だとは気付けていなかったです。
はい。ご指摘のとおりで、制約が変わり得ることを念頭において、初めから nums[0]を初期値とする、フラグを用いて初めは特殊対応するなど検討すべきでした。