目录
- 1 维基百科
- 2 53. 最大子数组和
- 2.1 代码思路
- 2.2 完整代码
- 3 918. 环形子数组的最大和
- 3.1 代码思路
- 3.2 完整代码
1 维基百科
最大子数组问题
在计算机科学中,最大子数组问题的目标是在数组的一维方向找到一个连续的子数组,使该子数组的和最大。例如,对一个数组 [ − 2 , 1 , − 3 , 4 , − 1 , 2 , 1 , − 5 , 4 ] \mathrm{[−2, 1, −3, 4, −1, 2, 1, −5, 4]} [−2,1,−3,4,−1,2,1,−5,4],其连续子数组中和最大的是 [ 4 , − 1 , 2 , 1 ] \mathrm{[4, −1, 2, 1]} [4,−1,2,1],其和为 6 \mathrm{6} 6。
Kadane 卡丹算法
卡丹算法扫描一次整个数组的所有数值,并在每一个扫描点计算以该点数值为结束点的子数组的最大和。
该子数组由两部分组成:以前一个位置为结束点的最大子数组和、该位置的数值。因为该算法用到了「最佳子结构」(以每个位置为结束点的最大子数组和都是基于其前一位置的最大子数组和计算得出的),该算法可看成动态规划的一个例子。
由于只需要记忆前一位置的最大子数组和,因此不需要定义 d p \mathrm{dp} dp 数组,只需要定义一个变量即可。
2 53. 最大子数组和
2.1 代码思路
思路说明图如下,其中 p r e \mathrm{pre} pre 表示以当前位置为结尾的最大子数组和。已经求得以 i = 2 \mathrm{i=2} i=2 位置为结尾的最大子数组和是 − 2 -2 −2,现在需要求解以 i = 3 \mathrm{i=3} i=3 位置为结尾的最大子数组和。如下图所示:
针对 i = 3 \mathrm{i=3} i=3 位置,它既可以接入前一子数组,也可以另起一个新子数组。由于接入前一子数组的和为 p r e = − 2 + 4 = 2 \mathrm{pre=-2+4=2} pre=−2+4=2,而另起一个新子数组的和为 p r e = 4 > 2 \mathrm{pre=4>2} pre=4>2,因此以 i = 3 \mathrm{i=3} i=3 位置为结尾的最大子数组和是 4 \mathrm{4} 4。
说明:之所以在图中把已经访问过的位置遮盖起来,是因为我认为没有必要考虑前面位置的子数组长什么样,容易把自己绕晕。除此之外, p r e \mathrm{pre} pre 表示的是「以当前位置为结尾」的最大子数组和,而非最大子数组和。
2.2 完整代码
int maxSubArray(vector<int>& nums) {
int pre = 0;
int ans = INT_MIN;
for (auto & num : nums) {
pre = max(pre + num, num);
ans = max(ans, pre);
}
return ans;
}
3 918. 环形子数组的最大和
3.1 代码思路
根据题意可得,最大子数组和可能来源于以下两种情况:
其中「情况一」可以直接用「53. 最大子数组和」的解法进行处理,「情况二」可以理解为「子数组和 = 前缀和 + 后缀和」。我们可以在处理「情况一」的同时顺便计算前缀和,以便「情况二」进行使用。
3.2 完整代码
int maxSubarraySumCircular(vector<int>& nums) {
int n = nums.size();
int ans = nums[0];
// 情况一(同时计算前缀和、最大前缀和)
int pre = nums[0];
int preSum = nums[0];
vector<int> preMax(n);
preMax[0] = nums[0];
for (int i = 1; i < n; ++i) {
pre = max(pre + nums[i], nums[i]);
ans = max(ans, pre);
preSum += nums[i];
preMax[i] = max(preMax[i - 1], preSum);
}
// 情况二(计算后缀和)
int posSum = 0;
for (int i = n - 1; i > 0; --i) {
posSum += nums[i];
ans = max(ans, preMax[i - 1] + posSum);
}
return ans;
}
变量说明
- p r e \mathrm{pre} pre:以 i \mathrm{i} i 位置为结尾的最大子数组和;
- p r e S u m \mathrm{preSum} preSum:前缀和;
- p r e M a x \mathrm{preMax} preMax:以 i \mathrm{i} i 位置为结尾的最大前缀和;
- p o s S u m \mathrm{posSum} posSum:后缀和。
针对「情况二」,由于选用的前缀不能和当前的后缀重叠,因此针对以 i \mathrm{i} i 位置为开头的后缀,它能考虑的最大前缀和是 p r e M a x [ i − 1 ] \mathrm{preMax[i - 1]} preMax[i−1]。