原题链接
这题用动态规划…对于动态规划,我只是粗略地知道当一个问题类似递归的形式,且存在某个子问题被重复的求解时,可以用空间换时间的方式缩短求解的时间长度。也就是说,每次求解一个子问题,那就将这个子问题的解存储下来,这样当球规模更大的同一pattern的问题时,就不必要继续递归到底,而是直接取出子问题的解代入就行了。这是我阅读《算法导论》的动态规划章节得到的思路。
按照这个思路,我们来看这个问题。首先我们肯定可以发现这个问题是可以分解的。我们应当注意这种分解方式是将问题分解成核心类似,但是规模不同的多个子问题。然后这些子问题的子问题——也就是核心——的类似使得前一个子问题的求解是可以被后一个子问题复用的。就拿题目中的例子
n
u
m
s
=
[
−
2
,
1
,
−
3
,
4
,
−
1
,
2
,
1
,
−
5
,
4
]
nums = [-2,1,-3,4,-1,2,1,-5,4]
nums=[−2,1,−3,4,−1,2,1,−5,4]而言,每个元素都可以作为一个连续子数组的结尾。我们将nums的所有连续子数组的情况分为9种连续子数组的情况,或者说以第i个下标元素结尾的所有连续子数组的情况可以组成一个集合,那么nums的所有连续子数组就可以分成九个集合。然后每一个集合中都可以找到某个数组其元素总和最大,那么这个数组就是以下标i的元素为结尾的连续子数组中和最大的连续子数组,那么九个集合就可以选拔出九个最佳选手进入九强。之后比较这九个连续子数组的元素和哪个是最大的,那么就是nums的最大和连续子数组。
那么问题来了,这样的分解方式有没有重复的工作呢?当然是有的!
我们先假设以下标为i的元素为结尾的连续子数组集合为
s
[
i
]
s[i]
s[i],然后以下标为i的元素为结尾的连续子数组集合中那个最大和的连续子数组为
p
[
i
]
p[i]
p[i],那个最大和的值为
f
[
i
]
f[i]
f[i]。你会发现s[i]中的所有的连续子数组无非只有两种情况:
- nums[i]本身就是一个连续子数组。
- s[i-1]中的所有连续子数组后面加个nums[i],就变成新的连续子数组了。
这就导致我们对连续子数组的最大和的讨论必然会有很多重复工作!比方说你费尽千辛万苦求了s[i-1]中的所有连续子数组的和,正当你要列出所有以下标为i的元素的连续子数组的情况,组成s[i],然后再这些数组的元素和时,你发现:我为什么不直接用s[i-1]的工作呢?s[i-1]中每个连续子数组的元素总和的再加一个nums[i]就是s[i]的连续子数组的元素总和(当然别忘了nums[i]这单一个元素的连续子数组)。同理,p[i]必然是从p[i-1]拼接上nums[i]组成的新连续子数组和nums[i]两个对手中比较诞生优胜者。或者用官方解答的正式说法:
f
(
i
)
=
m
a
x
f
(
i
−
1
)
+
n
u
m
s
[
i
]
,
n
u
m
s
[
i
]
f(i)=max{f(i-1)+nums[i],nums[i]}
f(i)=maxf(i−1)+nums[i],nums[i]
到这里我们的思路就变成了:遍历nums数组,求出每一个元素的f(i),然后存入f数组中。再遍历f数组,找到这个最大的f(i)。但是到这里我们还可以从空间上再次优化一下,就是有点类似于冒泡排序——或者说滚动数组吧——那种感觉。我每次计算出f(i)的时候,我就直接和f(1)到f(i-1)中的的最大值对决,对决胜出的成为擂主。等到计算出f(i+1)时,我就拿f(i+1)和f(1)到f(i)中的最大值——也就是擂主——进行对决。因此我们就只用一个变量
m
a
x
A
n
s
maxAns
maxAns标记擂主就可以了。i=1时f(1)就是maxAns,i等于2是我们只需要比较上一个迭代maxAns和f(2)看看需不需要更新maxAns就可以了…然后和maxAns每次比较的f(i)也是通过上一个迭代的f(i)+nums[i]和nums[i]比较决出的,那么我们干脆就只用一个变量
p
r
e
pre
pre去表示上一个迭代的f(i)(也就是f(i-1)),每个迭代更新这个pre值,然后再根据更新的pre值和MaxAns比较即可。
这也就是官方解答给出的动态规划解法:
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
}
这道题还有很多细节可以深挖,具体恐怕要先鸽着了,往后慢慢看。