最大子数组的线性时间求解问题

本文探讨如何使用动态规划在线性时间内解决最大子数组问题。通过分析算法导论中的思路,提出了一个非递归的线性时间算法。首先定义dp数组,并通过递推方程实现状态转移。接着,介绍了如何通过滚动数组优化空间复杂度。最后,简单提及了贪心算法在这个问题上的应用,虽然与dp类似,但贪心需要证明其正确性。

之前有一个博客说的是使用分治的方式处理问题,但是我们可以看到实际上是将问题复杂化了。
链接:最大子数组问题

动态规划

dp说白了就是一个个的不断尝试,来找到问题的最优解,那么动态规划就应该有一个状态转移方程,我们来看看这道题用动态规划能不能解决?

算法导论还是很良心的,至少给了我们以下的思路:

使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。从数组的左边界开始,从左至右处理,记录到目前为止已经处理过的最大子数组。若已知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]的最大子数组。

先看看这句话什么意思吧。
我们已经有了一个[1……j]的最大子数组,那么在此基础上我们就可以求出A[1……j+1]的最大子数组,只可能有两种情况:还是原来的,或者是舍弃前面的一部分,A[i……j+1]。(这部分比较直观)
然后就是线性时间求[i……j+1],这个就是一个个的遍历找到i。

如果我们给出的dp数组的定义是这样的dp[i] = A[1……i]最大子数组的值,那么写递推方程会变成这样:

dp[j+1] = max{ dp[j],ΣA[i……j+1] }

好家伙,这啥啊。
而且最后我们只能通过一个个i的尝试来寻找最优解,那么时间肯定不是线性阶的。

修改dp数组
网上流行的方式是将dp[i]定义为以i结尾的子数组的最大解
那么 dp[j] = max{ A[i……j] },1<= i &

最大子数组是连续的若干数组元素,若其和是最大的,那么这个子数组就称为该数组的最大子数组,它是很多问题的抽象,比如购买股票,把相邻两天的股价之差作为数组元素,求在连续的某个时间段内买入股票的最佳时间和卖出股票的最佳时间就可以抽象为计算最大子数组问题 [^3]。 求解最大子数组问题有分治法和贪心算法(这里结合动态规划思想的变种贪心算法,即Kadane算法)等方法: - **分治法**:将数组 `X[1…n]` 分为 `X[1…n/2]` 和 `X[n/2 +1…n]`,递归求解问题,包括数组 `X[1…n/2]` 的最大子数组 `S1`、数组 `X[n/2+1…n]` 的最大子数组 `S2`,然后计算跨中点的最大子数组 `S3`,数组 `X` 的最大子数组之和 `Smax = max{S1, S2, S3}`,时间复杂度为 $O(nlogn)$ [^5]。 ```python def find_max_crossing_subarray(arr, low, mid, high): left_sum = float('-inf') sum_val = 0 max_left = mid for i in range(mid, low - 1, -1): sum_val += arr[i] if sum_val > left_sum: left_sum = sum_val max_left = i right_sum = float('-inf') sum_val = 0 max_right = mid + 1 for j in range(mid + 1, high + 1): sum_val += arr[j] if sum_val > right_sum: right_sum = sum_val max_right = j return max_left, max_right, left_sum + right_sum def find_max_subarray(arr, low, high): if high == low: return low, high, arr[low] else: mid = (low + high) // 2 left_low, left_high, left_sum = find_max_subarray(arr, low, mid) right_low, right_high, right_sum = find_max_subarray(arr, mid + 1, high) cross_low, cross_high, cross_sum = find_max_crossing_subarray(arr, 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 arr = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7] low, high, max_sum = find_max_subarray(arr, 0, len(arr) - 1) print(f"最大子数组的起始下标: {low}, 结束下标: {high}, 最大和: {max_sum}") ``` - **Kadane算法**:从数组左边界开始,由左到右处理,记录到目前为止已处理过的最大子数组。若已知 `A[1...j]` 的最大子数组,`A[1...j+1]` 的最大子数组要么是 `A[1...j]` 的最大子数组,要么是某个子数组 `A[i...j+1]` 的最大子数组(`1 <= i <= j+1`),在已知 `A[1...j]` 的最大子数组的情况下,可以在线性时间内找出形如 `A[i...j+1]` 的最大子数组时间复杂度为 $O(n)$ [^1]。 ```python def max_subarray_sum(arr): max_ending_here = max_so_far = arr[0] for num in arr[1:]: max_ending_here = max(num, max_ending_here + num) max_so_far = max(max_so_far, max_ending_here) return max_so_far arr = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7] print(f"最大子数组和: {max_subarray_sum(arr)}") ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值