题意描述:
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
进阶: 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
示例:
示例 1:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
解题思路:
Alice:这道题目也很熟悉啊。
Bob: 对啊,大二的算法课讲动态规划的话基本上一定会讲这个例子的。
Alice: 动态规划 ? Dynamic planning ? ?
Bob: 对啊,我们先来看看题目吧。求一个连续子数组的最大和,我们可以暴力求解。
Alice: 那样的话,要写一个双层循环了,外面的 i
从 0
到数组末尾
, 里面的 j
从 i+1
到数组末尾
。
Bob: 我们可以用前序和来省略一些计算,前序和是这样的,假如我们有一个数组[1,2,3,4]
这个数组对应的前序和数组就是[1, 3, 6, 10]
前序和数组中第 i
个元素就是 原数组 中前 i
个元素的和。这样的话,如果我们想要求原数组中 第 j
个元素到第 k
个元素的和,只要用前序和数组中的 第 k
个元素减去 第 j-1
个元素就可以了。
Alice: 这样就能省略计算吗 ?
Bob: 对啊,还是拿[1, 2, 3, 4]
来举例子吧, 假如我要算[1, 2]
的和,要计算 1 + 2
,要计算[1, 2, 3]
的和就要计算1+ 2 + 3,1 + 2
就被重复计算了呀。用前序和算的话,每个元素都只被加了一次,求任意的子数组的和只要一个减法就够了。
Alice: 但是前序和也是O(n^2) 的两重循环啊。
Bob: 是啊,虽然减少了计算量,不过前序和 + 暴力求解应该还是会超时的。
Alice: 这题不是动态规划吗 ? 你还记得动态规划的写法吗 ?
Bob: 不记得了,不过我们可以现写一个啊。
Alice: 我记得动态规划的核心就是递推公式,递推公式有点像数学里面的那个 归纳法 里面的那个从 n
推出 n+1
的情况。
Bob: 从 n 推出 n + 1 ?? 假设我们现在已经求出了前 n 个元素的最大子序和,把第 n+1 个 元素加上 ??
Alice: 不对呀,最大子序和要求的是连续的子数组啊, 前 n 个元素对应的最大子序和 对应的子数组不一定是 以 第 n 个元素结尾的啊。
Bob: 是啊,那就假设我们已经求出了 以 第 n 个元素为结尾的子数组的最大子序和吧,然后现在我们面对一个选择。
Alice: 对于以 第 n+1 个元素为结尾的子数组的子序和 要不要加上 前面 n 个元素的最大子序和 ? 以第 n+1 个元素为结尾的子序和既可以是 只有 第 n+1 个元素, 也可以是连接上 前面的一些元素。
Bob: 对,如果 以第 n 个元素为结尾的子数组的最大子序和 <= 0 就不加了,加了也只能减小以第 n+1 个元素为结尾的子序和。
Alice: w(゚Д゚)w 我们已经把递推公式写出来了。
if dp[n] > 0:
dp[n+1] = dp[n] + nums[n+1]
else:
dp[n+1] = nums[n+1]
Bob: w(゚Д゚)w 对啊,(๑•̀ㅂ•́)و✧
Alice: 动态规划就是O(n)的了,好快啊。那你会写什么 更精妙的 分治法吗?
Bob: (・ω・`ll) 明天就会写了。
代码:
Python 方法一 : 动态规划
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if len(nums) == 0:
// 若nums 长度为0,那么最大子序和一定是0
return 0
else:
answers = [x for x in nums]
// answers[x] 用来记录 以 nums[x] 为结尾的前面的子数组的最大子序和
for x in range(1, len(nums)):
if answers[x-1] <= 0:
// 如果以nums[x-1]为结尾的子数组的最大子序和是个负数,那么以nums[x]为结尾的子数组的最大子序和就是 nums[x] 自身
answers[x] = nums[x]
else:
answers[x] = answers[x-1] + nums[x]
// 否则以nums[x] 为结尾的子数组的最大子序和应该加上以nums[x-1]为结尾的子数组的最大子序和,就是延续。
return max(answers)
Python 方法二: 前序和 + 暴力求解,确认超时 !!!
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
else:
preSums = [0]
preSum = 0
for x in range(0, len(nums)):
preSum += nums[x]
preSums.append(preSum)
ret = nums[0]
for x in range(0, len(preSums)):
for z in range(x+1, len(preSums)):
if(preSums[z] - preSums[x] > ret):
ret = preSums[z] - preSums[x]
return ret
Java 方法一: 动态规划
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 0){
return 0;
}else{
int subsequenceSum = nums[0]; // 记录以nums[x]为结尾元素的子数组的最大子序和
int ret = subsequenceSum; // nums数组的最大子序和
for(int i=1; i<nums.length; ++i){
if(subsequenceSum <= 0){ // 动态规划之核心的递推公式
subsequenceSum = nums[i];
}else{
subsequenceSum += nums[i];
}
if(subsequenceSum > ret){ // “打擂台”得到一个最大值
ret = subsequenceSum;
}
}
return ret;
}
}
}
c++ 方法一: 动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size() == 0){
return 0;
}else{
int subSum = nums[0];
int maxSubSum = subSum;
for(int i=1; i<nums.size(); ++i){
if(subSum <= 0){
subSum = nums[i];
}else{
subSum += nums[i];
}
if(maxSubSum < subSum){
maxSubSum = subSum;
}
}
return maxSubSum;
}
}
};
易错点:
[-2,1,-3,4,-1,2,1,-5,4]
[0]
[1,2,3]
[-1,-2, -3]
[-2, 1, 0, 4]
总结:
最大子序和,将前面积累的和为负的子数组抛弃,将新的元素作为子序和的开头。此之谓,当断而断。