一个有N个整数元素的一维数组(A[0],A[1],...,A[n-1]),那么这个数组的子数组之和的最大值是什么呢?
理解题意:
- 题目所说的子数组是连续的。
- 题目只需要求和,并不需要返回子数组的具体位置。
- 数组的元素是整数,所以数组可能会出现正整数、零、负整数。
解法 1
使用前n项和的算法来做:
nums = [1,-2,3,5,-3,2]
n = len(nums)
sum = [0]*(n+1)
for i in range(n):
sum[i+1] = sum[i] + nums[i]
maximum = -2**31
for i in range(n,0,-1):
for j in range(i-1,-1,-1):
if sum[i] - sum[j] > maximum:
maximum = sum[i]-sum[j]
print(maximum)
优化:
nums = [1,-2,3,5,-3,2]
n, maximum =len(nums), -2**31
for i in range(n):
sum = 0
for j in range(i,n):
sum += nums[j]
if sum > maximum:
maximum = sum
print(maximum)
解法 2
将所给的数组(A[0],...,A[n-1])分成长度相同的两段数组(A[0],...,A[n/2-1])和(A[n/2],...A[n-1]),分别求出这两段数组各自的最大子段和,则原数组的最大子段和为以下三种情况的最大值:
- A[0],...,A[n-1]最大字段和和A[0],...,A[n/2-1]最大字段和相同。
- A[0],...,A[n-1]最大字段和和A[n/2],...A[n-1]最大字段和相同。
- A[0],...,A[n-1]最大字段和跨越其中间两个元素A[n/2-1]到A[n/2]。
该算法的时间复杂度为:O(N*logN)。
解法 3
从方法2中可以得到提示:可以考虑数组的第一个元素A[0],以及最大的一段数组(A[i],...,A[j])之间的关系有以下三种情况:
- 当0=i=j时,元素A[0]本身构成和的最大一段。
- 当0=i<j时,和最大的一段以A[0]开始。
- 当0<i时,和最大的一段与A[0]没有关系。
从上面的三种情况可以看出,可以将一个大问题(N个元素数组)转化为一个较小的问题(n-1个元素数组)。假设已经知道(A[1],...,A[n-1])中和最大的一段数组之和为All[1],并已经知道(A[1],..,A[n-1])中包含A[1]的和最大的一段数组为Start[1]。那么根据上述分析,我们可以得到(A[0],...,A[n-1])中最大子数组和为max(A[0],A[0]+Start[1],All[1])。那么根据这个规律我们可以很轻松的从数组末端节点开始遍历到数组开始节点。该算法的时间复杂度为O(N)。
A = [1,-2,3,5,-3,2]
n, maximum =len(A), -2**31
All = [0]*n # All[i]表示A[i],...,A[n-1]的最大一段子数组
Start = [0]*n # Start[i]表示包含A[i]的最大一段子数组和只有A[i]一个数的最大字段和
# 初始化n
All[-1],Start[-1] = A[-1], A[-1]
for i in range(n-2,-1,-1):
Start[i] = max(A[i], A[i]+Start[i+1])
All[i] = max(Start[i],All[i+1])
print(All[0])
优化空间:因为Start,All都的值都只与前一个状态有关,因此可以用变量来表示。
A = [1,-2,3,5,-3,2]
n, maximum =len(A), -2**31
# 初始化n
All,Start = A[-1], A[-1]
for i in range(n-2,-1,-1):
Start = max(A[i], A[i]+Start)
All = max(Start,All)
print(All)
换一种写法:
A = [1,-2,3,5,-3,2]
n, maximum =len(A), -2**31
# 初始化n
All,Start = A[-1], A[-1]
for i in range(n-2,-1,-1):
if Start < 0:
Start = 0
Start += A[i]
if Start > All:
All = Start
print(All)