Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions leetcode/53/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# 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)

## 他の解法

Follow upでdivide and conquerが仄めかされていたので、どうにか問題を分割できないか考えていたのだが、思いつかなかった。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

役に立つかはわかりませんが、以下自分の理解です。

与えられた配列を中央で分けて、左の部分配列と右の部分配列でそれぞれ最大の和を再帰的に探していきます。が、中央を跨ぐ部分配列も最大の和が作れる候補になり得るので、

  • 分割
    • 配列を中央で分ける
  • 統治
    1. 左半分の最大和を再帰的に求める
    2. 右半分の最大和を再帰的に求める
    3. 中央を跨ぐ最大和を求める(配列の左端と右端を広げる方向に動かし、和が最大になる部分を見つける)
  • 結合
    • i.~iii.で最も大きいものを最大和とする

のような関数を用意して実装する必要があります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

丁寧にありがとうございます!まだ中央を跨ぐ最大和の扱いがしっくり頭に収まらないのですが、もう少し考えてみます...

全体から見ると最大になる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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_subarray_sum は int なので、float("inf") ではなく sys.maxsize を選ぶ考え方もあるようです。

An integer giving the maximum value a variable of type Py_ssize_t can take
https://docs.python.org/3/library/sys.html#sys.maxsize

Copy link
Copy Markdown
Owner Author

@huyfififi huyfififi Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントありがとうございます!私は、sys.maxsizeはプラットフォーム依存・Pythonのintには上限がなくsys.maxsizeを超えうる、といった問題がある一方、PythonではDuck-Typingが推奨されているような気がするので、floatintのミスマッチは大きな問題ではないと考えています。また、sys.maxsizelistなどの最大データ長を表し、それをとりうる最大の値として用いるのは、少しミスリーディングかな?とも思っています。が、実際他の方々がどう思うかはわかりません...


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
```

- [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")
max_ending_subarray_sum = 0
for num in nums:
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

読みやすかったです。特に問題ないと思います。

```

## その他

### Divide and Conquer

LeetCodeのSolutionsをざっと眺めて、一晩寝てもよくわからなかった。LeetCodeのSolutions上に残してあるコメントや、一社内のPRを見て予想するに、多くの人は分割統治法を思いつけない・書けないし、現在の私の理解力を超えているように思うので、一旦スキップすることにする。後で戻ってきた時にわかるようになっているかも。

### 変数名

Kadane's Algorithmの解法の変数に対して、端的で明確な名付けができなかった。変数名がやけに長くなってもしょうがないし、ある程度短いものにして、あとは処理から推測してもらう方がいいだろうか。

# Step 3

初期値が`0`でも`float("-inf")`でもいい時(`max_ending_sum`)、どちらを採用しようか迷う。
また、先にDPの解法があってから空間計算量を削ってKadane's Algorithm、という流れを欠いて、Kadane's Algorithmだけを一発目で見ると理解に時間がかかるような気がする。
15 changes: 15 additions & 0 deletions leetcode/53/step1.py
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions leetcode/53/step2_kadane.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions leetcode/53/step2_prefix_sum.py
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions leetcode/53/step3_kadane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Solution:
def maxSubArray(self, nums: list[int]) -> int:
max_ending_sum = 0
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
10 changes: 10 additions & 0 deletions leetcode/53/step3_prefix_sum.py
Original file line number Diff line number Diff line change
@@ -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