跳到正文
第 53 题中等

最大子数组和

Maximum Subarray

在一个可能有负数的数组中,找一个连续子数组,使它的元素和最大。

在力扣中练习

题目描述

给你一个整数数组 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

 

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

 

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

解题思路

主解法

Kadane · 以 i 结尾

前缀变成负数就丢掉,否则继续接

暴力枚举左右端点是 O(n²)。设 current 为以 nums[i] 结尾的最大子数组和,那么下一步只有两种选择:

  • 接上前面的:current + nums[i];
  • 从当前数重新开始:nums[i]。

取较大值。如果 current 已经是负数,接上它只会让结果更小,所以等价于丢掉前缀、从当前数重新开始。

自拟主例 [3,-5,4,2,-1,3]:接上 -5 后 current 变成 -2;遇到 4 时丢掉负前缀,从 4 重新开始,最后 4+2-1+3=8。

分步图解

接上前面:3 + -5 = -2;全局最大 3。

current
-2
best
3
nums
0
1
2
3
4
5
3
-5
4
2
-1
3
i
以 i 结尾的当前和 -2,全局最大 3
best
3
目前见过的最大子数组和
1 / 3
接上 3 后 current 变成 -2;遇到 4 时丢掉负前缀,从 4 重新开始。

best 单独记录所有 current 的最大值,因为最优子数组不一定在末尾结束。

分步图解

接上前面:6 + -1 = 5;全局最大 6。

current
5
best
6
nums
0
1
2
3
4
5
3
-5
4
2
-1
3
i
以 i 结尾的当前和 5,全局最大 6
best
6
目前见过的最大子数组和
1 / 3
4、2、-1、3 连成 8;即使中途下降,只要 current 仍为正就继续接。

时间 O(n),空间 O(1)。

时间 O(n)空间 O(1)

易错点

  • current 和 best 初始化为 0:数组全是负数时会返回 0,而不是最大负数。
  • 只更新 current 不更新 best:答案可能停在数组中间。
  • 认为 current 必须一直为正:它可以暂时为负,只要下一次加上 nums[i] 还能变大。
对照解法

分治 · 跨中点合并

最大子数组要么在左半、要么在右半、要么跨中点

时间 O(n log n),比 Kadane 慢;但分治框架可以扩展到二维最大子矩阵等更复杂的问题。

把数组从中点切开,最大子数组只有三种位置:完全在左半、完全在右半、跨过中点。

左右两半递归求解;跨中点的情况,分别从 mid 向左、从 mid+1 向右累加,记录最大后缀和与最大前缀和,相加就是跨中点的最优值。

时间 O(n log n),递归栈 O(log n)。

  1. 1递归求解左半、右半。
  2. 2从 mid 向左累加,求最大后缀和。
  3. 3从 mid+1 向右累加,求最大前缀和。
  4. 4三者取最大。
时间 O(n log n)空间 O(log n)

易错点

  • 跨中点时只取一边:必须同时包含 mid 和 mid+1 两侧的连续段。
  • 递归终止条件写成 l >= r:单元素区间应返回该元素。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入nums = [3,-5,4,2,-1,3]

从第一个数开始:current = best = 3;空子数组不允许,所以不能用 0 初始化。

current
3
nums
0
1
2
3
4
5
3
-5
4
2
-1
3
i
以 i 结尾的当前和 3,全局最大 3
best
3
目前见过的最大子数组和
1 / 7
查看 C++ 代码

延伸阅读

  • Kadane 的本质是“以 i 结尾”的状态压缩;和打家劫舍、乘积最大子数组共享同一套状态定义。
  • 如果要求输出区间,需要额外记录起点;本页图解已经用窗口标出了当前区间。