最大子数组,最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
本题在leetcode中属于简单题目,但是这题其实有很多解法,都蕴涵了很多算法思想。分别有暴力解法、动态规划解法和分治思想解法。在此做一个总结。
暴力解法
直接两两配对,暴力求解
简单的尝试每对可能的组合。n个数共有(n 2)中组合。这种方法的运行时间为Ω(n²).
def maxSubArray(nums:List[int]) -> int:
nums_len = len(nums)
max_num = max(nums) # 只考虑一个时的最大值
for i in range(0,nums_len): # 双层for循环,最少两两配对时,逐个求和,得到最大值
for j in range(i+1,nums_len):
sum_num = sum(nums[i:j+1])
if sum_num > max_num:
max_num = sum_num
return max_num
使用分治策略的求解方法
假定我们要寻找子数组A[low..high]的最大子数组。使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low..mid]和A[mid+1..hidg]。A[low..high]的任何连续子数组A[i..j]所处的位置必然是以下三种情况之一:
可以发现,在递归的调用时,问题变成了如何求得跨越中点的数组和最大值。
- 完全位于子数组A[low..mid]之中,因此low<=i<=j<=mid。
- 完全位于子数组A[mid+1..high],因此 mid< i<=j<=high。
- 跨越了中点,因此low <=i<=mid < j<=high。
可以发现,在递归的调用时,问题变成了如何求得跨越中点的数组和最大值。
下面是求解跨越中点的最大和子数组 可以看到数组A[low..high]包含n个元素,则调用find_max_crossing_subarray将花费θ(n)的时间。
def find_max_crossing_subarray(A,low,mid,high):
left_sum = -float('inf')
sum_ = 0
max_left = mid
for i in range(mid,low-1,-1): # 求左所半部分A[low..mid]的最大子数组
sum_ = sum_ + A[i]
if sum_ > left_sum:
left_sum = sum_
max_left = i
right_sum = -float('inf')
sum_ = 0
for i in range(mid+1,high+1): # 求出右半部分A[mid+1..high]的最大子数组
sum_ = sum_ + A[i]
if sum_ > right_sum:
right_sum = sum_
max_right = i
return max_left,max_right,left_sum + right_sum
有了线性时间的find_max_crossing_subarray,我们可以得出求解最大子数组问题的分治算法的伪代码。
def find_maximum_subarray(A,low,high):
if high == low:
return low,high,A[low]
else:
mid = (low+high)//2
left_low,left_high,left_sum = find_maximum_subarray(A,low,mid) # 求解左半部分的最大子序和范围
right_low,right_high,right_sum = find_maximum_subarray(A,mid+1,high) # 求解右半部分的最大子序和范围
cross_low,cross_high,cross_sum = find_max_crossing_subarray(A,low,mid,high) # 求解跨越中点的最大子序和范围
if left_sum >= right_sum and left_sum >= cross_sum:
return left_low,left_high,left_sum
elif right_sum >= left_sum and right_sum >= cross_sum:
return right_low,right_high,right_sum
else:
return cross_low,cross_high,cross_sum
这种方法的时间复杂度为θ(nlgn)
最大子数组问题实际上存在一个线性时间的算法
思路: 从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1..j]的最大子数组,基于如下性质将解扩展为A[1..j+1]的最大子数组:A[1..j+1]的最大子数组要么是A[1..j]的最大子数组,要么是某个子数组A[i..j+1]( 1 <= i <= j+1)。在已知A[1..j]的最大子数组的情况下,可以在线性时间内找出形如A[i..j+1]的最大子数组。
def find_maximum_array(nums):
if not nums:
return None # 如果nums为空,返回空
sum_ = 0
max_sum = nums[0]
for i in range(len(A)):
sum_ = sum_ + A[i] # sum_ 记录sum_
if sum_ > max_sum:
max_sum = sum_
if sum_ < 0:
sum_ = 0
return max_sum