🔥个人主页: Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
一、题目背景
LeetCode 53 题「最大子数组和」是算法面试中的经典问题,属于动态规划与前缀和的典型应用。题目要求在给定整数数组中,找到一个连续子数组(最少包含一个元素),使其和最大并返回该值。
例如:
- 输入
nums = [-2,1,-3,4,-1,2,1,-5,4],输出6(对应子数组[4,-1,2,1])。 - 输入
nums = [5,4,-1,7,8],输出23(对应子数组[5,4,-1,7,8])。
这道题不仅考察算法思维,还能帮助理解「前缀和」「动态规划」等核心概念,是大厂面试的高频考点。
二、核心思路:前缀和 + 最小前缀和
1. 前缀和的定义
对于数组 nums,定义前缀和数组 sum,其中 sum[i] 表示从 nums[0] 到 nums[i] 的累加和:
sum[i]=nums[0]+nums[1]+⋯+nums[i]
那么,子数组 nums[j+1 ... i] 的和可以表示为:
sum[i]−sum[j]
我们的目标是找到 j < i,使得 sum[i] - sum[j] 最大。
2. 转化问题:找最小前缀和
对于每个 i,要让 sum[i] - sum[j] 最大,等价于找到最小的 sum[j](其中 j < i)。
因此,我们可以维护两个变量:
sum:当前的前缀和(sum[i])。min_sum:遍历到当前位置时,最小的前缀和(min{sum[0], sum[1], ..., sum[i-1]})。
每遍历一个元素,我们先更新当前前缀和 sum,然后计算 sum - min_sum(即当前子数组的最大可能和),并维护全局最大值 Max。最后更新 min_sum 为当前最小的前缀和。
3. 初始值的关键设置
sum初始化为0,表示前缀和从空数组开始累加。min_sum初始化为0,对应sum[-1] = 0(空数组的前缀和),确保第一个元素也能正确计算。Max初始化为INT_MIN,处理数组全为负数的情况(例如nums = [-5,-3,-2],最大子数组和为-2)。
三、代码实现(C++)
cpp
运行
#include <vector>
#include <climits>
using namespace std;
class Solution {
public:
// 子数组可以看成子数组最右边的前缀和减去它前面的最小前缀和
int maxSubArray(vector<int>& nums) {
int sum = 0;
int Max = INT_MIN;
int min_sum = 0;
for (int i = 0; i < nums.size(); ++i) {
sum += nums[i];
Max = max(Max, sum - min_sum); // 计算当前子数组的最大和
min_sum = min(min_sum, sum); // 更新最小前缀和
}
return Max;
}
};
四、代码逐行解析
-
变量初始化:
sum = 0:当前前缀和,初始为空数组的和。Max = INT_MIN:全局最大子数组和,初始为最小整数,确保能正确更新负数情况。min_sum = 0:最小前缀和,初始为空数组的和。
-
遍历数组:
sum += nums[i]:累加当前元素,更新前缀和。Max = max(Max, sum - min_sum):计算以当前元素结尾的子数组的最大和(当前前缀和减去之前的最小前缀和),并更新全局最大值。min_sum = min(min_sum, sum):更新最小前缀和,确保后续计算能用到更小的前缀和。
-
返回结果 :遍历结束后,
Max即为整个数组的最大子数组和。
五、示例验证(以 nums = [-2,1,-3,4,-1,2,1,-5,4] 为例)
索引 i |
nums[i] |
sum(前缀和) |
sum - min_sum |
Max |
min_sum |
|---|---|---|---|---|---|
| 0 | -2 | -2 | -2 - 0 = -2 | -2 | min(0,-2)=-2 |
| 1 | 1 | -1 | -1 - (-2) = 1 | 1 | min(-2,-1)=-2 |
| 2 | -3 | -4 | -4 - (-2) = -2 | 1 | min(-2,-4)=-4 |
| 3 | 4 | 0 | 0 - (-4) = 4 | 4 | min(-4,0)=-4 |
| 4 | -1 | -1 | -1 - (-4) = 3 | 4 | min(-4,-1)=-4 |
| 5 | 2 | 1 | 1 - (-4) = 5 | 5 | min(-4,1)=-4 |
| 6 | 1 | 2 | 2 - (-4) = 6 | 6 | min(-4,2)=-4 |
| 7 | -5 | -3 | -3 - (-4) = 1 | 6 | min(-4,-3)=-4 |
| 8 | 4 | 1 | 1 - (-4) = 5 | 6 | min(-4,1)=-4 |
最终,Max = 6,与示例输出一致。
六、复杂度分析
- 时间复杂度 :
O(n),仅需遍历数组一次,每个元素的操作都是常数时间。 - 空间复杂度 :
O(1),仅使用了常数个变量,未额外开辟数组空间。
七、解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 前缀和 + 最小前缀和 | O(n) |
O(1) |
追求时间、空间效率最优 |
| Kadane 算法(动态规划) | O(n) |
O(1) |
直观理解「当前子数组和是否延续」 |
| 分治法 | O(n log n) |
O(log n) |
理解分治思想,适合教学场景 |
前缀和 + 最小前缀和的解法与 Kadane 算法效率相同,但思路更偏向数学推导,适合从前缀和的角度理解问题。
八、总结
LeetCode 53 题的核心是将「最大子数组和」转化为「前缀和的差值最大化」问题,通过维护最小前缀和,在一次遍历中完成计算,时间和空间复杂度均为最优。这种思路不仅能解决本题,还可拓展到类似的子数组和问题(如返回子数组的起始 / 结束索引)。