Skip to content
Open
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
204 changes: 204 additions & 0 deletions 53_maximum_subarray/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# 53. maximum-subarray

https://leetcode.com/problems/maximum-subarray/description/

## Comments
最大値を求める問題か。
全ての部分配列を求めて、最大値を求めるのはO(n^2)かな。
流石に無理そう。

Inputの数は、10^5までやし、
O(n^2)ではなくて、O(n)で求める必要があるなぁ。

Maxを求める方法は?
まずは、1個1個のマスに対して、最大値を求める。
最大値は、前のマスの最大値(自分自身を除く)OR 自分自身を含めた部分列の和、の2択かな。

もっと具体的に考えると、1個1個のマスに対して、最大値を求める方法は、
1. そのマスを含む部分配列の最大値を求める。
2. そのマスを含まない部分配列の最大値を求める。
3. そのマスを含む部分配列の最大値と、含まない部分配列の最大値を比較して、最大値を求める。

直前の値を利用するのなら動的計画法かな。
一旦まずは、dpにcopyした方が楽そう?
実装できそうやし手を動かしてみよう。


Main関数で何個かテストしてみたところ、あってそうな感じする。提出してみよう。

![提出結果]({EA17A920-E9C3-44E7-9D6F-FE87CB3DE6DB}.png)

OK!
けど計算時間もうちょい短く出来そう?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LeetCodeの時間計測は誤差が大きいので、timeコマンド等を使ってご自身の環境で計測して評価するのも良いかと思いました。

準備、計測方法は以下が参考になると思います。
yukib0123/LeetCode#19 (comment)

memcopyとdpと最大値で3回ループしてるからか。

1週に減らせへんかなぁ。
とりあえず、memcopyは必要無さそうか。
んで、下のwhile文は、一回目に統合しよう。

![二回目の提出結果]({36527AFF-BB15-4B4D-9ACB-B004040EBF56}.png)

できた!
けど可読性は少し悪くなったかぁ。
これなら前の実装の方が良いかも。
てか、これmemcopyしてるのと同じか??

memory量もすこし減らせそう。dp配列2つしか使用してないし。

```c
int maxSubArray(int *nums, int numsSize)
{
int *dp;
int i;
int max_sum;

dp = malloc(sizeof(int) * 2);
if (!dp)
return (0);
dp[0] = nums[0];
max_sum = dp[0];
i = 1;
while (i < numsSize)
{
dp[1] = nums[i];
if (dp[1] < dp[0] + nums[i])
dp[1] = dp[0] + nums[i];
if (max_sum < dp[1])
max_sum = dp[1];
dp[0] = dp[1];
i++;
}
free(dp);
return (max_sum);
}
```
![3回目の提出結果]({292A41D3-5729-4613-B35D-E42A39C15863}.png)

うーん。
改善できたけど、可読性は悪くなった。
他の人の答案を見てみよう。

命名の仕方いい人見つけた。
dpって命名良くないな。意味を込めたほうが良さそう。
あと、直前の値(dp[0])もいらんのかなぁ。

```c
int maxSubArray(int* nums, int numsSize) {
int currentSum = nums[0];
int maxSum = nums[0];
for (int i = 1; i < numsSize; i++) {
if (currentSum < 0)
currentSum = nums[i];
else
currentSum += nums[i];

if (currentSum > maxSum)
maxSum = currentSum;
}
return maxSum;
}
```
参考にしたコード。

なんでこれでいけんのや??
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

あ、このコードを読んで、「なんでこれでいけんのや」と思ったということは、よりよい説明や変数名があった可能性があります。
「いま見ている位置 i を終点とする区間、で取り得る最大和」と読めなかったということですよね。

直前の値が負なら場合分け。
うーん。いまいち分からん。
ChatGPTに聞いてみるか。

```md
### 何をしているコードか ―― “最大部分配列問題” の高速解(Kadane 法)

与えられた整数配列 `nums` の **連続した区間のうち、要素の合計が最大になるもの** を 1 回の走査で求めています。
ポイントは

| 変数 | 役割 |
| ------------ | ------------------------------- |
| `currentSum` | **いま見ている位置 i を終点とする区間**で取り得る最大和 |
| `maxSum` | これまでに見つかった **全区間のうちの最大和** |

---

## 行ごとの意味

```c
int currentSum = nums[0]; // 最初の区間は要素0だけ
int maxSum = nums[0];

for (int i = 1; i < numsSize; i++) {
/* 直前までの区間が負なら、拡張すると必ず損をする */
if (currentSum < 0)
currentSum = nums[i]; // 区間をリセット(ここから新しく始める)
else
currentSum += nums[i]; // 区間をそのまま右へ 1 要素伸ばす

/* ここまでで得られた最大値を更新 */
if (currentSum > maxSum)
maxSum = currentSum;
}
return maxSum;


---

## “なぜ負ならリセット?” を直感で

* 連続区間の末尾に新しい要素 `nums[i]` を足すとき、
もし **直前の合計 `currentSum` が負** なら
`currentSum + nums[i] < nums[i]` が必ず成り立つ ⟶ *必ず悪化*
→ だったら “マイナスごと切り捨てて” `nums[i]` からやり直す方が得。
* 正の合計を持っていれば、伸ばした方が大きくなる *可能性* があるので、そのまま足す。

---

## 具体例シミュレーション

配列 `[-2, 1, -3, 4, -1, 2, 1, -5, 4]` を手で追うと:

| i | nums\[i] | currentSum の計算 | currentSum | maxSum |
| - | -------- | --------------------- | ---------- | ------ |
| 0 | -2 | 初期値 | **-2** | **-2** |
| 1 | 1 | -2 < 0 → リセット → 1 | 1 | 1 |
| 2 | -3 | 1 ≥ 0 → 1 + (-3) = -2 | -2 | 1 |
| 3 | 4 | -2 < 0 → リセット → 4 | 4 | **4** |
| 4 | -1 | 4 ≥ 0 → 4 + (-1) = 3 | 3 | 4 |
| 5 | 2 | 3 ≥ 0 → 3 + 2 = 5 | 5 | **5** |
| 6 | 1 | 5 ≥ 0 → 5 + 1 = 6 | 6 | **6** |
| 7 | -5 | 6 ≥ 0 → 6 + (-5) = 1 | 1 | 6 |
| 8 | 4 | 1 ≥ 0 → 1 + 4 = 5 | 5 | 6 |

最終結果 `maxSum = 6`(区間 `[4, -1, 2, 1]`)。

---

## 性能

* **時間計算量**:O(N) ― 配列を 1 回なめるだけ
* **追加メモリ**:O(1) ― 変数 2 つだけ

---

## より読みやすく書き換える例

```c
int maxSubArray(int *a, int n) {
int best = a[0];
int here = a[0];

for (int i = 1; i < n; ++i) {
here = (here < 0) ? a[i] : here + a[i];
best = (best < here) ? here : best;
}
return best;
}

同じ考え方で、全要素が負でも安全に動きます。
理解の助けになれば幸いです!
```


なるほど!!
めっちゃ単純な話で、直前が負なら、足した結果は必ず元の数字より小さくなるもんな。

Step3に再実装しといた。
まあほぼfor文をwhile文に変えただけやけど。

41 changes: 41 additions & 0 deletions 53_maximum_subarray/step1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int maxSubArray(int *nums, int numsSize)
{
int *dp;
int i;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

読み手が上から読んで行った時に覚えておくべき変数を減らせるという観点から、i をループの直前、あるいはfor (int i = 1; i < numsSize; i++)のように書くのも良いと思いました。

今回は覚えておくべきものが少ないので問題にならないと思いますが。

int max_sum;

dp = malloc(sizeof(int) * numsSize);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i番目までのmax_sumという意味でmax_sum_so_farなどつけた方が理解の助けになる情報があって良いと思います。

if (!dp)
return (0);
memcpy(dp, nums, sizeof(int) * numsSize);
i = 1;
while (i < numsSize)
{
if (dp[i] < dp[i - 1] + nums[i])
dp[i] = dp[i - 1] + nums[i];
i++;
}
max_sum = dp[0];
i = 0;
while (i < numsSize)
{
if (max_sum < dp[i])
max_sum = dp[i];
i++;
}
free(dp);
return (max_sum);
}

int main(void)
{
int nums[] = {1, 2, -3, 4};
int numsSize = 4;

printf("%i\n", maxSubArray(nums, numsSize));
return (0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

C++のスタイルガイドからの引用で恐縮ですが、()付きのreturnはあまり見ない気がします。
(Cのガイドか何かに沿ったものでしたらすみません。。)

Do not needlessly surround the return expression with parentheses.

https://google.github.io/styleguide/cppguide.html#Return_Values

}
28 changes: 28 additions & 0 deletions 53_maximum_subarray/step2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int maxSubArray(int *nums, int numsSize)
{
int *dp;
int i;
int max_sum;

dp = malloc(sizeof(int) * numsSize);
if (!dp)
return (0);
dp[0] = nums[0];
max_sum = dp[0];
i = 1;
while (i < numsSize)
{
dp[i] = nums[i];
if (dp[i] < dp[i - 1] + nums[i])
dp[i] = dp[i - 1] + nums[i];
if (max_sum < dp[i])
max_sum = dp[i];
i++;
}
free(dp);
return (max_sum);
}
25 changes: 25 additions & 0 deletions 53_maximum_subarray/step3.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int maxSubArray(int *nums, int numsSize)
{
int currentSum;
int i;
int maxSum;

currentSum = nums[0];
maxSum = nums[0];
i = 1;
Copy link
Copy Markdown

@fuga-98 fuga-98 Jun 3, 2025

Choose a reason for hiding this comment

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

C言語あまり知らないのですが、
int currentSum = nums[0];
int maxSum = nums[0];
int i = 1;
みたいにしない理由ってありますか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

42(という学校)のスタイルがちょっと変わっていてこんな感じです。
(教育的配慮からくるものですね。)
まあ、こういうスタイルもあるくらいに思っていただけるといいかと思います。

while (i < numsSize)
{
if (currentSum < 0)
currentSum = nums[i];
else
currentSum += nums[i];
if (maxSum < currentSum)
maxSum = currentSum;
i++;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ぶら下がりif文は事故が起きやすいので、私は避けます。
https://discord.com/channels/1084280443945353267/1084283898617417748/1370794488607543306

}
return (maxSum);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.