中–难 · 一趟遍历一个念头

最大子段和

数组里所有连续的一段中,哪一段的和最大?暴力解法是 O(n²)。而那个关键的洞察只有一句话。

一句话答案Kadane 算法 —— running = max(a[i], running + a[i]),best = max(best, running);因为要求区间非空,全负数的数组也算得对。

题目

输入: 第一行一个整数 n(1 ≤ n ≤ 1000)。第二行 n 个空格隔开的整数(−1000 ≤ ai ≤ 1000)。可能有负数。

输出: 非空连续子段可能达到的最大和。

样例:

输入输出
8
-2 1 -3 4 -1 2 1 -5
6
5
-3 -1 -7 -2 -5
-1
1
5
5

第一个样例里最好的一段是 4 -1 2 1 = 6。第二个样例全是负数,但你仍然必须选一段 —— 答案是 −1,不是 0。大多数解法就挂在这里。

你的解答

main.cpp — 你的解答
「运行我的输入」用的自定义输入

判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。

卡住了?提示

提示 1 — 先看暴力解法,体会它有多浪费

枚举每个起点 i、每个终点 j,求和取最大。这是 O(n²) —— n = 1000 时大约一百万次加法,而且大部分都在重复计算你已经算过的和。快解法从不回头看。

提示 2 — 一个「当前累计」就够了

维护 cur = 以当前位置结尾的最优子段和。下一个数 x 到来时,你只有两个选择:接上(cur + x),或者在 x 处重新开始。取较大的那个。然后如果 cur 超过了 best 就更新 best。

提示 3 — 那一句话的洞察

如果 cur 是负的,它只会把下一个数往下拖 —— 扔掉它,重新开始。就这一句:if (cur < 0) cur = x; else cur = cur + x;。其余都是记账。

题解(先自己试试!)
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int best, cur;
    cin >> best;
    cur = best;
    for (int i = 1; i < n; i++) {
        int x;
        cin >> x;
        if (cur < 0) cur = x;
        else cur = cur + x;
        if (cur > best) best = cur;
    }
    cout << best << endl;
    return 0;
}

这是 Kadane 算法,也是动态规划的标准入门:cur 是一个状态,它用一个数概括了你需要知道的全部过去。你并没有记住数组 —— 你只记住了唯一重要的那件事。这种重新表述 ——「我必须往前带的最小信息是什么」—— 就是这门手艺的全部。它也是闯关链的最后一题:你已经走过了输入输出、循环、数组、字符串、位运算、排序、查找和一个真正的算法。回头看看题库列表,数数有多少道你能不看提示做出来。

深度解析

为什么这样做对

维护 cur = 以当前位置结尾的最优子段和。下一个数到来时你只有两个选择:接上,或者从它重新开始。取大的那个。

代价

O(n) 一趟,只多两个变量。枚举所有区间是 O(n²) —— 而且其中大部分加法都在重复计算已有的和。

常见错法

  • 把 best 初始化成 0:全是负数时输出 0,但空子段是不允许的,正确答案是最大的那个负数。
  • 用 if (x > 0) 而不是 if (cur < 0) 决定接不接 —— 这会硬接上一段本该扔掉的负累计,最大和就偏小了。

下一步

闯关到这里结束。回头看看 第一题 —— 你已经走了很远。

记住这两句

  • 负的累计只会拖累你 —— 扔掉它,从当前这个数重新开始。
  • 动态规划的核心问题:我必须往前带的最小信息是什么?
🐞 这段是错的 —— 点开,亲手让它挂一次

best 从 0 开始 —— 全是负数时输出 0,但空子段是不允许的

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    int best = 0, cur = 0;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        if (cur < 0) cur = x;
        else cur = cur + x;
        if (cur > best) best = cur;
    }
    cout << best << endl;
    return 0;
}