题目链接
https://leetcode.com/problems/maximum-subarray/
题目描述
给定整数数组nums,找到具有最大和的至少包含一个元素的连续子数组,返回其最大和。
示例
Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.
解决思路
这个问题将采用动态规划算法来解决。首先参考《剑指offer》,解答这样一个问题:什么题目可以考虑用动态规划算法去求解。
如果题目满足以下三个特征:
1、求一个问题的最优解(通常是求最大值或最小值);
2、该问题的最优解依赖各个子问题的最优解,子问题的最优解组合起来能够得到整个问题的最优解(最优子结构);
3、该问题能被分解为若干个子问题,这些子问题之间还有相互重叠的更小的子问题
那么我们就可以考虑用动态规划来解决这个问题。
由于子问题会互相重叠,为了避免重复求解子问题,可以按照从下往上的顺序先求小问题的最优解并存储下来,再在此基础上求大问题的最优解。做到从上往下分析问题,从下往上求解问题。
【动态规划理论】:一篇文章带你彻底搞懂最优子结构、无后效性和重复子问题这篇文章定义了“一个模型三个特征”,将动态规划算法解决问题的模型概括为“多阶段决策最优解模型”,在解决问题过程中需要经历多个决策阶段,每个决策阶段对应一组状态。算法目的就是找到一组决策序列,经过这组决策序列,能够产生最终期望求解的最优值。
同时,对于是适合用动态规划解决的问题,文章定义了三个特征:最优子结构、无后效性、重复子问题。
1、最优子结构
最优子结构指问题的最优解包含子问题的最优解,即可以通过子问题的最优解,推导出问题的最优解。
2、无后效性
无后效性有两层含义,第一层含义是,在推导后面阶段状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常“宽松”的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。
3、重复子问题
不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。
确定问题可以由动态规划算法解决后,首先寻找最优子结构(可以先分析问题中的递推关系,通过寻找规律得到最优子结构)。对于最大子数组和的问题,最优子结构可以定义为“以nums[i]为结尾的连续子数组的最大和”,并用f(i)来表示。如果已知f(i-1)即以nums[i-1]为结尾的连续子数组的最大和,f(i)有两个取值:f(i-1)+nums[i]和nums[i]。要看f(i-1)是否为正数,如果为正数,则f(i)取值为f(i-1)+nums[i];如果为负数,则f(i)取值为nums[i]:f(i) = max(f(i-1)+nums[i],nums[i])。对于长度为n的数组,f(0).....f(n-1)的最大值就是最后返回的结果。
Python实现
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
for i in range(1,len(nums)):
if nums[i-1] > 0:
nums[i] = nums[i] + nums[i-1]
return max(nums)
在代码中,可以直接用nums[i]存储f(i)的值,num[i-1]即为已经计算过的f(i-1)的值,通过判断f(i-1)是否为正数来判断是否更新nums[i]。
时间复杂度和空间复杂度
数组各元素仅访问一次,因此时间复杂度为O(n)。(python中min()、max()运算的时间复杂度为O(n))
空间复杂度为O(1)。
总结
最大子序列和问题于1977被Ulf Grenander of Brown University提出,1984年Cranegie Mellon University的Jay Kadane给出了一个线性时间复杂度的算法,被称为Kadane's Algorithm。有关子序列系列的问题可以在Kadane's Algorithm的思路、代码模板的基础上继续改进。
Kadane's Algorithm in Python
如果数组为空或数组中没有正数元素时,会返回0。
def max_subarray(numbers):
"""Find the largest sum of any contiguous subarray."""
best_sum = 0 # or: float('-inf')
current_sum = 0
for x in numbers:
#current_sum为0或正整数
current_sum = max(0, current_sum + x) #如果current_sum+x 大于0,则更新current_sum为current_sum+x;如果小于0,说明x一定小于0,则更新current_sum为0.
best_sum = max(best_sum, current_sum)
return best_sum
在此基础上的变体:如果数组中没有正数元素则返回数组最大值(这种设定更常见),如果数组为空,返回-inf。
def max_subarray(numbers):
"""Find the largest sum of any contiguous subarray."""
best_sum = float('-inf')
current_sum = 0
for x in numbers:
current_sum = max(x, current_sum + x)
best_sum = max(best_sum, current_sum)
return best_sum
拓展题目