Python 算法题之 子数组的最大和
给出题目🍠
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组,求所有子数组的和的最大值
比如输入
nums = [-2,1,-3,4,-1,2,1,-5,4]
,其中的最大子数组是 [4,-1,2,1],算法应当输出 6
注意:
此问题要求 计算子数组的最大和,而子数组一定是连续的,这就是为什么上面的示例 最大子数组中有个 -1,而不把除了负数之外的值全部加起来,为获取最大子数组和,那就得经过 -1
此问题与此前提到的 最长递增序列比较类似
暴力算法
这道题相对简单,只要掌握如何获取并且保存 最大值即可
- 循环每一个
nums[i:]
的值并求和,保存求和的最大值
def max_sub_array(nums: list) -> int:
res, length = float("-inf"), len(nums)
for i in range(length):
res_i = 0 # 每次循环结束,把res清0,不继承nums[i:]的最大值
for k in range(i, length):
res_i += nums[k] # 将nums[i:] 中的值循环求和
if res < res_i: # 当nums[i:] 循环求和中的结果 大于 已知最大值时,重新给最大值赋值
res = res_i
return max_res # 此时返回的是最大值
- 时间复杂度: O(n㏒n) ,其中
for k in range(i, length)
中的 i 会随着第一层循环逐渐减少 - 空间复杂度: O(1)
动态规划
问题要求的是子数组,而不是子序列,那么就能想出,dp[i]
只有两种选择
- 要么与前面相邻子数值相连,要么不与前面的子数组相连,自成一派
- 根据这两种选择,比较容易能想出,状态转移方程为
max(nums[i], nums[i]+dp[i-1])
,最简单的情况下 (base case) 即为自身nums[i]
def max_sub_array(nums: list) -> int:
length = len(nums)
dp = [0] * length
dp[0] = nums[0] # 第一个元素前面没有子数组,这里手动添加最简单的情况下自身值
for i in range(1, length): # 跳过dp[0],从dp[1] 开始
dp[i] = max(nums[i], dp[i - 1] + nums[i])
return max(dp)
当然,也可以直接给 DP Table 一个初始值 解决 第一个元素前面没有子数组的问题
def max_sub_array(nums: list) -> int:
length = len(nums)
dp = [0] * (length+1) # 为 DP Table 增加一个初始值
for i in range(1, length):
dp[i] = max(nums[i-1], dp[i - 1] + nums[i-1])
return max(dp)
- 时间复杂度: O(n)
- 空间复杂度: O(n)
存储前两个状态
仔细观察 状态转移方程,可以发现 dp[i]
只是和它的左邻居 dp[i-1]
有关系,这和 斐波那契数列 相似,斐波那契数列 只需要存储前两个状态即可,那么 最大子数组和 是否也能做到呢?
def maxSubArray(self, nums: List[int]) -> int:
prev = res = nums[0] # pres 代表 dp[i-1], res为dp[0]最简单的情况下自身值
length = len(nums)
for i in range(1, length):
curr = max(nums[i], prev+nums[i])
prev = curr
res = max(res, curr) # 保存子数组和最大值
return res
可以看到,确实能够参考 斐波那契数列 那样只存储前两个状态 ,从而实现 状态压缩 优化
- 时间复杂度: O(n)
- 空间复杂度: O(1),因为只是存储前两个状态,所以从O(n)降到了O(1)
参考资料😘
- 题目出之力扣:leetcode 连续子数组的最大和
由衷感谢💖