Rearch Interest: Visualization">
53. 最大子序和
题目描述
给定一个整数数组nums
,
找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例1: >输入: [-2,1,-3,4,-1,2,1,-5,4]
>输出:
6
>解释: 连续子数组[4,-1,2,1]
的和最大,为
6
。
进阶: 如果你已经实现复杂度为\(O(n)\)的解法,尝试使用更为精妙的分治法求解。
我的代码 1 (Timeout)
1 | class MySolution |
方法一: 动态规划
\(T(N) = O(N)\), \(S(N) = O(N)\)
假设 nums
数组的长度是 n
,下标从
0
到 n-1
。 sums[i]
:
以第i
个数nums[i]
结尾的 连续子数组最大和;
sums[0]
显然等于nums[0]
;
则我们求的就是max{sums[i]}, 0<=i<n
;
如何求每个sums[i]
呢?
考虑每个sums[i]
是nums[i]
单独成一段,还是在nums[i]
的基础上加入sums[i-1]
。
即sums[i] = max{nums[i], nums[i]+sums[i-1]}
。
1 | class Solution |
我的代码 2: 动态规划
\(T(N) = O(N)\), \(S(N) = O(N)\)
假设 nums
数组的长度是
n
,下标从0
到 n-1
。
sums[i]
: 以第i
个数nums[i]
开头的
连续子数组最大和;
sums[n-1]
显然等于nums[n-1]
;
则sums[i] = max{nums[i], nums[i]+sums[i+1]}
。
1 | class MySolution |
方法二: 贪心法
\(T(N) = O(N)\), \(S(N) = O(1)\)
从左向右迭代,一个个数字nums[i]
加过去,和为sum
;
则当前的max=Max{max,sum}
.
在nums[i]
处,当sum>(=)0
时,
认为在下一步i+1
时继续使用这个叠加结果会为当前连续最大值带来增益,
因此在通过比较max
值确定是否保存当前sum
为最大值之后可以继续使用sum
。
在nums[i]
处,当sum<0
时,
若在下一步i+1
再使用这个已为负值的叠加结果则会削弱当前连续最大值,
因此不再使用该sum
,在通过比较max
值确定是否保存当前sum
为最大值之后,将其清零并重新从nums[i+1]
开始叠加(即重新考虑新的连续子串)。
{.line-numbers} 1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution
{
public int maxSubArray(int[] nums)
{
int sum = 0; int max = Integer.MIN_VALUE;
for (int i=0; i<nums.length; i++)
{
sum+=nums[i];
max = Math.max(sum, max);
if (sum<0) sum=0;
}
return max;
}
}
1 | class Solution |
方法三: 分治法 (太复杂了且不是最优解)
\(T(N) = O(N)\), \(S(N) = O(logN)\)
这个分治方法类似于「线段树求解 LCIS
问题」的pushUp
操作。
我们定义一个操作get(nums, l, r)
表示查询nums
序列[l, r]
区间内的最大子段和,
那么最终我们要求的答案就是get(nums, 0, nums.size() - 1)
。
如何分治实现这个操作呢?
对于一个区间[l, r]
,我们取m = ⌊(l + r)/(2)⌋
,对区间[l, m]
和[m + 1, r]
分治求解。
当递归逐层深入直到区间长度缩小为1的时候,递归「开始回升」。
这个时候我们考虑如何通过[l, m]
区间的信息和[m + 1, r]
区间的信息
合并成区间[l, r]
的信息。
最关键的两个问题是: - 我们要维护区间的哪些信息呢? - 我们如何合并这些信息呢?
对于一个区间[l, r]
,我们可以维护四个量: -
lSum
表示[l, r]
内以l
为左端点的最大子段和; -
rSum
表示[l, r]
内以r
为右端点的最大子段和; -
mSum
表示[l, r]
内的最大子段和; -
iSum
表示[l, r]
的区间和;
以下简称[l, m]
为[l, r]
的「左子区间」,[m + 1, r]
为[l, r]
的「右子区间」。
我们考虑如何维护这些量呢(如何通过左右子区间的信息合并得到[l, r]
的信息)?
对于长度为1
的区间[i, i]
,四个量的值都和nums[i]
相等。
对于长度大于1
的区间: - iSum
:
区间[l, r]
的iSum
就等于「左子区间」的iSum
加上「右子区间」的
iSum
。 - lSum
: 对于[l, r]
的
lSum
: - 要么等于「左子区间」的 lSum
; -
要么等于「左子区间」的iSum
加上「右子区间」的
lSum
; 二者取大。 - rSum
:
对于[l, r]
的rSum
: - 要么等于「右子区间」的
rSum
; - 要么等于「右子区间」的 iSum
加上「左子区间」的rSum
; 二者取大。 - mSum
:
考虑 [l, r]
的 mSum
对应的区间是否跨越
m
: - 它可能不跨越
m
,也就是说[l, r]
的 mSum
可能是「左子区间」的 mSum
和 「右子区间」的
mSum
中的一个; - 它也可能跨越
m
,可能是「左子区间」的rSum
和 「右子区间」的
lSum
求和; 三者取大。
这样问题就得到了解决。
1 | class Solution |