最大子段和
思路
经典中的经典——最大子段和,也叫最大子数组和。给你一个整数数组,找一段连续的子数组,使得它的元素之和最大。
最暴力的做法当然是枚举所有子数组,算出每个的和取最大值,但那是 的,数据大了就扛不住。
这里用的是 Kadane 算法,核心思想特别简单:我们维护一个变量 cur 表示"以当前元素结尾的最大子段和"。每读入一个新元素,就把它加到 cur 上,然后用 cur 去更新全局答案。如果 cur 变成负数了呢?那说明前面这一段已经是累赘了,不如从下一个元素重新开始,所以把 cur 归零。
为什么这样是对的?因为一个负数的前缀只会拖累后面的元素,丢掉它一定不亏。而只要 cur 还是正的,往后延伸就有可能拿到更大的和,所以我们贪心地继续累加。
注意一个细节:maxSum 的初始值要设得足够小(比如 LLONG_MIN),因为数组可能全是负数,这时答案就是最大的那个负数,而不是 0。
代码
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n;
scanf("%d", &n);
long long maxSum = -1e18, cur = 0;
for (int i = 0; i < n; i++) {
long long x;
scanf("%lld", &x);
cur += x; // 把当前元素加入子段
maxSum = max(maxSum, cur); // 更新全局最大值
if (cur < 0) cur = 0; // 前缀和为负,果断丢弃
}
printf("%lld\n", maxSum);
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long maxSum = Long.MIN_VALUE, cur = 0;
for (int i = 0; i < n; i++) {
long x = sc.nextLong();
cur += x; // 把当前元素加入子段
maxSum = Math.max(maxSum, cur); // 更新全局最大值
if (cur < 0) cur = 0; // 前缀和为负,果断丢弃
}
System.out.println(maxSum);
}
}
import sys
input = sys.stdin.readline
def main():
n = int(input())
a = list(map(int, input().split()))
max_sum = float('-inf')
cur = 0
for x in a:
cur += x
if cur > max_sum:
max_sum = cur
if cur < 0:
cur = 0
print(max_sum)
main()
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', l => lines.push(l));
rl.on('close', () => {
const n = parseInt(lines[0]);
const a = lines[1].split(' ').map(Number);
let maxSum = -Infinity, cur = 0;
for (let i = 0; i < n; i++) {
cur += a[i];
if (cur > maxSum) maxSum = cur;
if (cur < 0) cur = 0;
}
console.log(maxSum);
});
复杂度分析
- 时间复杂度:
,只需要遍历数组一遍。
- 空间复杂度:
,只用了两个额外变量,甚至不需要把数组存下来。
小结
Kadane 算法是动态规划和贪心思想的完美融合。本质上 cur 就是 dp 状态——以当前元素结尾的最大子段和,转移方程是 dp[i] = max(dp[i-1] + a[i], a[i]),而 cur < 0 时归零就等价于选择"从当前元素重新开始"。整个过程一遍扫描、常数空间,是面试和竞赛中出现频率极高的经典题目。



京公网安备 11010502036488号