题目描述:
给定一个整数数组
n
u
m
s
nums
nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入:
[
−
2
,
1
,
−
3
,
4
,
−
1
,
2
,
1
,
−
5
,
4
]
[-2,1,-3,4,-1,2,1,-5,4]
[−2,1,−3,4,−1,2,1,−5,4],
输出:
6
6
6
解释: 连续子数组
[
4
,
−
1
,
2
,
1
]
[4,-1,2,1]
[4,−1,2,1] 的和最大,为
6
6
6。
这一题leetcode上并没有详细的分析,看别人的讲解也说的不是特别的详细,这里我按照自己的理解方式做一个分析。
首先这一题可以用动态规划算法,但是递推关系式的推出,是十分复杂的,其难度不应该是easy(当然仅从代码量角度看,确实是easy)。其python3实现代码如下
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
for i in range(1, len(nums)):
nums[i] = nums[i] + max(nums[i-1], 0)
return max(nums)
从代码可以看出递推关系式是 C [ i ] = C [ i ] + m a x ( C [ i − 1 ] , 0 ) C[i] = C[i] + max(C[i-1], 0) C[i]=C[i]+max(C[i−1],0)下面主要讲述这个递推关系式是怎么推出来的,主要用的扫描法,扫描法简单理解,就是上面的递推关系式,从 i = 0 i=0 i=0,递推(扫描)到 i = n − 1 i=n-1 i=n−1( n n n是 n u m s nums nums的长度),得到所有的 C [ i ] C[i] C[i]。通过扫描法,我们其实只考虑了 n n n个子序列,但是根据排列组合,有 1 + 2 + 3 + ⋯ + n = O ( n 2 ) 1+2+3+\dots +n = O(n^{2}) 1+2+3+⋯+n=O(n2)个子序列,因为长度为 n n n的子序列只有 1 1 1个,长度为 n − 1 n-1 n−1的子序列有 2 2 2个,长度为 n − 2 n-2 n−2的子序列有 3 3 3个,长度为 4 4 4……。我们证明的主要思路是, O ( n 2 ) O(n^{2}) O(n2)中的其余子序列情况的子序和一定小与等于扫描法考虑的n个子序列情况的子序和,因此才遗弃它们的。最后我们在 n n n个子序列中选出最大的即可。
首先说明一下 C [ i ] C[i] C[i]的含义,其表示从 n u m s [ 0 ] nums[0] nums[0]扫描到 n u m s [ i ] nums[i] nums[i],得到的包含 n u m s [ i ] nums[i] nums[i]的最大自序和,从两个子场景说明:
场景一:递推执行到索引 m m m,未出现 C [ i ] < 0 ( i = 0 , 1 , 2 , … m − 1 ) C[i]<0(i=0,1,2,\dots m-1) C[i]<0(i=0,1,2,…m−1)的情况,这样我们的递推式其实变成 C [ i ] = ∑ j = 1 i n u m s [ j ] C[i]= \sum\limits_{j=1}^{i}nums[j] C[i]=j=1∑inums[j],这样我们仅考虑了 m m m个子序的和,分别是 0 0 0到 0 0 0、 0 0 0到 1 1 1、 0 0 0到 2 2 2…… 0 0 0到 m − 1 m-1 m−1这 m m m个子序,子序开头全是 0 0 0,我们没有考虑的子序有两种情况,
情况一:以 m m m结尾,开头任意的子序,其示意图如下:
![](https://img-blog.csdnimg.cn/20190418100658440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fd2ludGVy,size_16,color_FFFFFF,t_70)
b
b
b代表情况一没有考虑到的任意一个子序的子序和,我们有
a
>
0
,
c
>
0
a>0,c>0
a>0,c>0这是场景一的假设,又
a
+
b
=
c
b
=
c
−
a
a+b=c\\b=c-a
a+b=cb=c−a所以
b
<
c
b<c
b<c,所以情况一所考虑的子序和
b
b
b,全部没有对应的场景一考虑的子序和
c
c
c大,所以被遗弃。
情况二:开头结尾都任意的子序。其示意图如下:
![](https://img-blog.csdnimg.cn/20190418101628872.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fd2ludGVy,size_16,color_FFFFFF,t_70)
b
b
b代表情况二没有考虑到的任意一个子序的子序和,我们有
a
>
0
,
d
>
0
a>0,d>0
a>0,d>0这是场景一的假设,又
a
+
b
=
d
b
=
d
−
a
a+b=d\\b=d-a
a+b=db=d−a所以
b
<
d
b<d
b<d,所以情况二所考虑的子序和
b
b
b,全部没有对应的场景一考虑的子序和
d
d
d大,所以被遗弃。至于
d
、
c
d、c
d、c谁更大,我们需要单独讨论。
场景二:递推执行到索引 m m m,第一次出现 C [ m − 1 ] < 0 C[m-1]<0 C[m−1]<0,言外之意 C [ i ] ≥ 0 ( i = 0 , 1 , 2 , … m − 2 ) C[i]\geq 0(i=0,1,2,\dots m-2) C[i]≥0(i=0,1,2,…m−2),这样我们的递推式还是 C [ i ] = ∑ j = 1 i n u m s [ j ] ( i = 0 , 1 , 2 , … m − 1 ) C[i]= \sum\limits_{j=1}^{i}nums[j](i=0,1,2,\dots m-1) C[i]=j=1∑inums[j](i=0,1,2,…m−1),但是 i > m − 1 i>m-1 i>m−1就要截断 n u m s nums nums,即从 m m m开始重新计算子序,有一种把索引 m m m当成索引 0 0 0的感觉,这是正常思维,如果不舍弃前面,加个负数的子序和一定小于舍弃前面的情况。但这样我们没有考虑的子序也有两种情况,
情况三:以 m m m结尾,开头任意的子序,其示意图如下(和情况一长得一样,但是 c < 0 c<0 c<0)
![](https://img-blog.csdnimg.cn/20190418100658440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fd2ludGVy,size_16,color_FFFFFF,t_70)
b
b
b代表情况三没有考虑到的任意一个子序的子序和,我们有
a
>
0
,
c
<
0
a>0,c<0
a>0,c<0这是场景二的假设,又
a
+
b
=
c
b
=
c
−
a
<
0
a+b=c\\b=c-a<0
a+b=cb=c−a<0所以
b
<
a
b<a
b<a,所以情况三所考虑的子序和
b
b
b,全部没有对应的子序和
a
a
a大,所以被遗弃。
情况四:跨过 m m m的子序。其示意图如下:
![](https://img-blog.csdnimg.cn/2019041810315566.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlb25fd2ludGVy,size_16,color_FFFFFF,t_70)
e
e
e代表情况四没有考虑到的任意一个子序的子序和,我们有
b
<
0
b<0
b<0这是情况三的推论,又
b
+
d
=
e
b+d=e
b+d=e所以
e
<
d
e<d
e<d,所以情况四所考虑的子序和
e
e
e,全部没有从
m
m
m开始重新考虑子序的子序和
d
d
d大,所以被遗弃。
接下来,我们就不断重复迭代场景一,场景二,就遗弃了其余的 O ( n 2 ) − n O(n^{2})-n O(n2)−n个子序,所以想推出上面的递推关系式,其分析是比较复杂的。
参考:
https://blog.csdn.net/zwzsdy/article/details/80029796
从一道easy leetcode问题,谈谈最大子列和的Kadane算法(强烈推荐):https://blog.csdn.net/the__apollo/article/details/77367534