最大子数组

最大子数组,最大子序和

给定一个整数数组 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]的一个最大子数组所处的位置必然是这三种情况之一 。实际上,A[low..high]的一个最大子数组必然是完全位于A[low..mid]中、完全位于A[mid+1..high]中或者跨越中点的所有子数组中和最大者。我们可以递归地求解A[low..mid]和A[mid+1..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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值