题目描述
这是一道超级经典的题目,对应的是力口上的第53题,最大子数组和。本文将会给出两种解法。一种是分治解决,一种是动态规划解决。
二分法
解决这个问题的核心想法是将数组不断地划分为更小的子数组,直到最小的子数组只包含一个元素。然后,通过递归的方式,不断地将子数组的最大子数组和合并,直到得到整个数组的最大子数组和。这里要特别注意,要如何处理最大字段在中间的情况,笔者给出的处理思路是用两个for循环,从中点向左右两端遍历,找到包含中点的最大子数组和。
具体代码实现如下:
import random
#处理跨界子段
def max_crossing_sum(arr, left, mid, right):
left_sum = float('-inf')
right_sum = float('-inf')
current_sum = 0
for i in range(mid, left-1, -1):
current_sum += arr[i]
if current_sum > left_sum:
left_sum = current_sum
current_sum = 0
for i in range(mid+1, right+1):
current_sum += arr[i]
if current_sum > right_sum:
right_sum = current_sum
return left_sum + right_sum
#二分法
def max_subarray_sum(arr, left, right):
if left == right:
return arr[left]
mid = (left + right) // 2
left_sum = max_subarray_sum(arr, left, mid)
right_sum = max_subarray_sum(arr, mid+1, right) #这里mid加1是因为在处理左半部分已经处理过一次mid了
cross_sum = max_crossing_sum(arr, left, mid, right)
return max(left_sum, right_sum, cross_sum)
# 测试
arr1 = [4, -5, 1, 2, -1, 4, -3, 1, -2]
print("测试1 最大子段和:", max_subarray_sum(arr1, 0, len(arr1)-1))
arr2 = [random.randint(-10, 10) for _ in range(100)]
print("随机数数组:", arr2)
print("测试2 最大子段和:", max_subarray_sum(arr2, 0, len(arr2)-1))
动态规划
接着给出动态规划解法,笔者在看到这到题目的时候,最开始的思路就是使用动态规划求解。具体思路是,使用一个dp数组来存储最大字段和,每次将目标数组中的元素加到dp数组中,比较当前dp数组中元素和与上一个状态的dp数组中元素和大小,取字段和最大的dp数组即可。
具体代码实现如下:
def max_subarray_sum_dp(arr):
n = len(arr)
dp = [0] * n
dp[0] = arr[0]
for i in range(1, n):
dp[i] = max(arr[i], dp[i-1] + arr[i])
return max(dp)
# 测试
arr1 = [4, -5, 1, 2, -1, 4, -3, 1, -2]
print("测试1 最大子段和:", max_subarray_sum_dp(arr1))
arr2 = [random.randint(-10, 10) for _ in range(100)]
print("随机数数组:", arr2)
print("测试2 最大子段和:", max_subarray_sum_dp(arr2))
动态规划的解法比二分的解法会更加简洁,也更符合人的思考思维。在实现动态规划算法过程中,我们的一个核心思路是得找状态转移方程,即如何描述从一个状态转移到另一个状态。