LeetCode 53. 最大子数组和
题目描述
给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = -2,1,-3,4,-1,2,1,-5,4
输出:6
解释:连续子数组 4,-1,2,1 的和最大,为 6。
示例 2:
输入:nums = 1
输出:1
示例 3:
输入:nums = 5,4,-1,7,8
输出:23
解法一:动态规划(Kadane 算法)⭐ 推荐
思路
经典的 Kadane 算法,利用动态规划思想,一次遍历即可求解。
定义 last 表示 以当前元素结尾的最大子数组和 。
对于每个元素 nums[i],要么将其接到之前的最优子数组后面(last + nums[i]),要么从当前位置重新开始(nums[i])。
即:
last = max(nums[i], last + nums[i])
代码中简化为 last = nums[i] + max(last, 0),当 last < 0 时相当于放弃之前的部分。
同时用 res 记录所有 last 中的最大值,即为答案。
代码实现
cpp
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN, last = 0;
for (int i = 0; i < nums.size(); i++) {
last = nums[i] + max(last, 0);
res = max(res, last);
}
return res;
}
};
复杂度分析
- 时间复杂度:O(n),仅需一次遍历。
- 空间复杂度:O(1),只使用常数个变量。
解法二:分治 / 线段树(更通用的思路)
思路
虽然 Kadane 足以解决本题,但分治法(线段树思想)更具扩展性,可轻松应对区间查询、单点修改等复杂问题。
我们定义一个结构体 Node,保存一个区间的四个关键信息:
sum:区间总和s:区间内最大子数组和(允许为空,即和为 0)ls:区间最大前缀和(从区间左端点开始,允许为空)rs:区间最大后缀和(到区间右端点结束,允许为空)
然后递归分治合并左右子区间的信息,合并公式如下:
res.sum = L.sum + R.sumres.s = max({L.s, R.s, L.rs + R.ls})
最大子数组可能完全在左、完全在右,或跨越左右(左后缀 + 右前缀)res.ls = max(L.ls, L.sum + R.ls)
最大前缀要么在左区间内,要么包含整个左区间加上右区间的最大前缀res.rs = max(R.rs, L.rs + R.sum)
最大后缀要么在右区间内,要么包含左区间的最大后缀加上整个右区间
⚠️ 注意 :由于我们将空子数组视为和为 0,所以当数组全为负数时,build 会返回 0,但题目要求非空子数组,因此需要特判全负数的情况,直接返回最大负数。
代码实现
cpp
class Solution {
public:
struct Node {
int sum; // 区间总和
int s; // 区间最大子数组和(允许为空)
int ls; // 区间最大前缀和(允许为空)
int rs; // 区间最大后缀和(允许为空)
};
Node build(vector<int>& nums, int l, int r) {
if (l == r) {
int v = max(nums[l], 0);
return {nums[l], v, v, v};
}
int mid = l + r >> 1;
auto L = build(nums, l, mid);
auto R = build(nums, mid + 1, r);
Node res;
res.sum = L.sum + R.sum;
res.s = max(max(L.s, R.s), L.rs + R.ls);
res.ls = max(L.ls, L.sum + R.ls);
res.rs = max(R.rs, L.rs + R.sum);
return res;
}
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
for (int x : nums) res = max(res, x);
if (res < 0) return res; // 全为负数,直接返回最大负数
auto t = build(nums, 0, nums.size() - 1);
return t.s;
}
};
复杂度分析
- 时间复杂度:O(n),递归遍历每个元素一次,合并操作 O(1)。
- 空间复杂度:O(log n),递归栈深度(树高)。
两种解法对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| Kadane 动态规划 | O(n) | O(1) | 仅需整个数组的最大子数组和,最简洁高效 |
| 分治 / 线段树 | O(n) | O(log n) | 更通用,支持区间查询、动态修改等扩展需求 |
对于本题,Kadane 算法足以轻松 AC。但掌握分治/线段树的思路,可以帮助你应对更多变形题目。
总结
- Kadane 代码简短、效率极高。
- 分治 思想经典,体现了"分而治之"及区间合并的通用技巧。
- 注意处理全负数边界情况,确保子数组非空。