给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。连续问题,其实相当于子串
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6
暴力法,
我们通过i和j记录子序列的左右边界,然后遍历所有的边界,寻找区间[i:j]和最大是多少即可。
时间复杂度O(n2) 空间复杂度 O(1)
import sys
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
maxsub = nums[0]
for i in range(0, len(nums) - 1):
mysum = 0
for j in range(i, len(nums) - 1):
mysum += nums[j]
if mysum > maxsub:
maxsub = mysum
return maxsub
我们这里只要求子序列的最大和,不要求得到子序列本身,其实就暗示了动态规划、分治
分治,我们把数组nums以中间位置m分为左(left)右(right)两部分, left = nums[0]…nums[m - 1] 和 right = nums[m + 1]…nums[n-1],对于每个区间 ,再像上述这样分治求解。当递归逐层深入直到区间长度缩小为1的时候,递归「开始回升」。这个时候我们考虑如何合并区间信息。
对于一个区间[l, r],我们可以维护四个变量:
lsum表示 [l, r] 内以 l 为左端点的最大子段和
rsum表示[l, r] 内以 r 为右端点的最大子段和
msum表示 [l, r] 内的最大子段和
isum表示 [l, r] 的区间和
对于长度为 1 的区间 ,四个量的值都相等。对于长度大于1 的区间:
isum: 等于「左子区间」的 isum 加上「右子区间」的 isum。
lsum: 它要么等于「左子区间」的 lsum,要么等于「左子区间」的 isum 加上「右子区间」的 lsum,二者取大。
rsum: 它要么等于「右子区间」的 rsum,要么等于「右子区间」的 rsum 加上「左子区间」的 isum,二者取大。
msum: 我们可以考它的最大字段是否跨越m——它可能不跨越 ,也就是说 msum可能是「左子区间」的 msum和「右子区间」的 msum 中的一个;它也可能跨越 ,可能是「左子区间」的 rsum 和 「右子区间」的 lsum 求和。三者取大。
这样问题就得到了解决。
时间复杂度: O(nlogn)
空间复杂度: O(logn) - 因为二分法,调用栈的深度最多是logn。
分治方法可以对任何区段【l, r】进行搜索
class Status:
def __init__(self, lsum, rsum, msum, isum):
self.lsum = lsum
self.rsum = rsum
self.msum = msum
self.isum = isum
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
return self.get_status(nums, 0, len(nums)-1).msum
def get_status(self, nums, left, right):
if left == right:
return Status(nums[left], nums[left], nums[left], nums[left])
else:
mid = (right + left) // 2
left_status = self.get_status(nums, left, mid)
right_status = self.get_status(nums, mid+1, right)
isum = left_status.isum + right_status.isum
lsum = max(left_status.lsum, left_status.isum + right_status.lsum)
rsum = max(left_status.rsum + right_status.isum, right_status.rsum)
msum = max(left_status.msum, right_status.msum, left_status.rsum+right_status.lsum)
return Status(lsum, rsum, msum, isum)
动态规划,动态规划的难点在于找到状态转移方程,
dp[i] - 表示一定要把当前位置 i 放进子序列的时候该连续子序列的最大和可以是多少,
明显这个时候 i 这个位置的元素是该子序列的最后一个元素
状态转移方程为: dp[i] = max(dp[i - 1] + nums[i], nums[i])
初始化:dp[0] = nums[0]
从状态转移方程中,我们只关注前一个状态的值,所以不需要开一个数组记录位置所有子序列和,只需要两个变量,
currMaxSum - 当前位置 i 的最大和
maxSum - 全局最大和:
currMaxSum = max(currMaxSum + nums[i], nums[i])
maxSum = max(currMaxSum, maxSum)
时间复杂度: O(n)
空间复杂度: O(1)
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
cur_max = 0
history_max = -inf
for cur in nums:
# 状态转移方程,当前状态的最大和
# dp[i] = max(dp[i - 1] + nums[i], nums[i])
cur_max = max(cur_max + cur, cur)
# 用当前状态的最大和去更新全局最大和
history_max = max(history_max, cur_max)
return history_max