时间复杂度:O(n)
解题思路
非常经典的动态规划题目!
以-2 11 -4 13 -5 -2为例:
步骤1:令状态dp[i]表示以nums[i]作为末尾的连续序列的最大和(这里是说nums[i]必须作为连续序列的末尾)。以样例为例:序列-2 11 -4 13 -5 -2,下标分别记为0,1,2,3,4,5,那么
- dp[0]=-2
- dp[1]=11
- dp[2]=7 (11-4=7)
- dp[3]=20 (11-4+13=20)
- dp[4]=15 (因为由dp数组的含义,nums[4]=-5必须作为连续序列的结尾,于是最大和就是11-4+13-5=15,而不是dp[3]的20)
- dp[5]=13 (11-4+13-5-2=13)
通过设置这么一个dp数组,要求的最大和其实就是dp[0],dp[1],...,dp[n-1]中的最大值(因为到底以哪个元素结尾未知),下面想办法求解dp数组。
步骤2:作如下考虑:因为dp[i]要求是必须以nums[i]结尾的连续序列,那么只有两种情况:
- 这个最大和的连续序列只有一个元素,即以nums[i]开始,以nums[i]结尾
- 这个最大和的连续序列有多个元素,即从前面某处nums[p]开始(p<i),一直到nums[i]结尾
对第一种情况,最大和就是nums[i]本身。
对第二种情况,最大和是dp[i-1]+nums[i],即nums[p]+...+nums[i-1]+nums[i]=dp[i-1]+nums[i]。
由于只有这两种情况,于是得到状态转移方程:
dp[i]=max{nums[i],dp[i-1]+nums[i]}
这个式子只和i与i前一个元素有关,且边界为dp[0]=nums[0],由此从小到大枚举i,即可得到整个dp数组。接着输出dp[0],dp[1],...,dp[n-1]中的最大值即为最大连续子序列的和。
这样只用O(n)的时间复杂度就解决了原先需要O(n²)复杂度问题,这就是动态规划的魅力!
通过观察我们发现,dp[i]只与dp[i-1]有关,故可以利用滚动数组的思想只用一个变量代替dp的一维数组。
AC代码
func maxSubArray(nums []int) int {
res:=nums[0]
for i:=1;i<len(nums);i++{
if nums[i-1]+nums[i]>nums[i]{
nums[i]+=nums[i-1]
}
if nums[i]>res{
res=nums[i]
}
}
return res
}
感悟
作为极为经典的动态规划题目,一定要熟练写出AC代码,特别要理解dp的含义,以及状态转移方程,这样才能更好地理解动态规划。
对于最大连续子序列和的题目,设计的动态规划模型为:
令dp[i]表示以nums[i]作为末尾的连续序列的最大和
此题目又可以用滚动数组的思想用一个变量代替dp数组,将空间复杂度从O(n)降到O(1)