1. 题目描述
给你一个整数数组 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
2. 解题思路
(1)贪心算法
在数组中,找出最大和的连续子数组。可以理解为查找局部最优解。那么此时可以想到使用贪心算法来实现。
局部最优解:当前“连续和”为负数时,立即放弃,从下一个元素重新计算。因为负数加上下一个元素,“连续和”只会越来越小。
全局最优:选取最大“连续和”
从代码角度上来讲:遍历 nums,从头开始 sum 累积,如果 sum 一旦加上 nums[i] 变为负数,那么就应该从 nums[i+1] 开始从 0 累积 sum,因为已经变为负数的 count,只会拖累总和。
这相当于是暴力解法中的不断调整最大子序和区间的起始位置。
(2)动态规划
由于当前最大子序列和只和前面的子序列和相关,因此也可以考虑使用动态规划的方式来进行。
- 确定 dp 数组及下标的含义:
- dp[i]:包括下标的最大连续子序列和为 dp[i]
- 确定递推公式
dp[i] 只有两个方向可以推出来:
- dp[i-1]+nums[i],即:nums[i] 加入当前连续子序列和
- nums[i],即:从头开始计算当前连续子序列和
一定取最大的,所以 dp[i]=max(dp[i-1]+nums[i],nums[i])
- dp 数组初始化
由递推公式可以看出,dp[i] 是依赖于 dp[i-1] 的状态,因此 dp[0] 就是递推公式的基础。dp[0]=nums[0]
- 确定遍历顺序
递推公式中 dp[i] 依赖于 dp[i-1] 状态,需要从前往后遍历。
- 举例推导 dp 数组
以示例一为例,输入:nums = [-2,1,-3,4,-1,2,1,-5,4],对应的dp状态如下:
3. 代码实现
贪心算法实现:
var maxSubArray = function(nums) {
let max=-Infinity;
let sum=0;
for(let i=0;i<nums.length;i++){
sum+=nums[i]
if(sum>max){
max=sum
}
if(sum<0){
sum=0
}
}
return max
};
- 时间复杂度:O(n),只进行一层遍历
- 空间复杂度:O(1)
动态规划实现:
var maxSubArray=function(nums){
const len=nums.length
let dp=new Array(len+1).fill(0)
dp[0]=nums[0]
let max=dp[0]
for(let i=0;i<len;i++){
let dp[i]=Math.max(dp[i-1]+nums[i],nums[i])
max=Math.max(max,dp[i])
}
return max
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)