给定一个实数序列,设计一个最有效的算法,找到一个总和最大的区间。
(与“寻找一只股票的最长的有效增长期”题目,即计算一只股票从哪天开始买进到哪天卖出的收益最大,表述不同,解法相同)
比如在下面的序列中:
1.5, −12.3, 3.2, −5.5, 23.2, 3.2, −1.4, −12.2, 34.2, 5.4, −7.8, 1.1, −4.9
总和最大的区间是从第5个数(23.2)到第10个数(5.4)。
假设此序列有 K K K个数: a 1 , a 2 , . . . , a K a_1, a_2, ..., a_K a1,a2,...,aK。
假定区间起始数字的序号是 p p p, 结束数字的序号是 q q q,区间数字的总和为 S ( p , q ) S(p, q) S(p,q),即 S ( p , q ) = a p + a p + 1 + . . . + a q S(p, q) = a_p + a_{p+1} + ... + a_q S(p,q)=ap+ap+1+...+aq。
区间左边界的序号为 l l l,右边界的序号为 r r r。
p p p取值可以从 1 1 1到 K K K, q q q取值从 p p p到 K K K,这是两重循环,复杂度为 O ( K 2 ) O(K^2) O(K2),循环的内部计算 S ( p , q ) S(p, q) S(p,q)平均要做 K / 4 K/4 K/4次加法,这是第三重循环,所以总的复杂度为 O ( K 3 ) O(K^3) O(K3)。
(自己根据理解写的代码)
# python
def method1(list):
l = -1
r = -1
Max = -10e100
for i in range(len(list)):
for j in range(i,len(list)):
temp = 0
for t in range(i, j):
temp += list[t]
# temp = sum(list[i:j])
if Max < temp:
l = i
r = j
Max = temp
print("区间左边界序号是:", l)
print("区间右边界序号是:", r)
print("最大总和是:", Max)
在方法一的最后加法循环中,做完 p p p到 q q q的加法,下一步重新再做 p p p到 q + 1 q+1 q+1的加法, p p p到 q q q的加法部分又重新做了一遍,出现了大量重复计算。简化方法是保存上一步的和,下一步再加一次就可以。
(自己根据理解写的代码)
# python
def method2(list):
l = -1
r = -1
Max = -10e100
for i in range(len(list)):
temp = list[i]
for j in range(i+1, len(list)):
temp += list[j] # 这里只需一步加法计算
if Max < temp:
l = i
r = j
Max = temp
print("区间左边界序号是:", l+1)
print("区间右边界序号是:", r+1)
print("最大总和是:", Max)
a = [1.5, -12.3, 3.2, -5.5, 23.2, 3.2, -1.4, -12.2, 34.2, 5.4, -7.8, 1.1, -4.9]
method2(a)
结果同上
思考题: 这种方法答案为什么是(1) [ p 1 , q 1 ] [p_1, q_1] [p1,q1], (2) [ p 2 , q 2 ] [p_2, q_2] [p2,q2], (3) [ p 1 , q 2 ] [p_1, q_2] [p1,q2]中的一种?
个人作答: 一个区间分成左右两部分,很显然最大子序列要么出现在左半边小区间,要么右半边小区间,要么就是跨左右小区间,所以答案是这三种的一种。
代码如下 [1]:(这里只计算了最大和,没有列出最大和对应区间边界,显然,是不会添加代码T.T)
# python
def method3(list):
n = len(list)
if n == 1:
return list[0]
else:
# 递归计算左半边最大子序和
max_left = method3(list[0:n//2])
# 递归计算右半边最大子序和
max_right = method3(list[n//2:n])
# 计算中间的最大子序和,从右到左计算左边的最大子序和,
# 从左到右计算右边的最大子序和,再相加
max_l = list[n // 2 - 1]
temp = 0
for i in range(n//2-1, -1, -1):
temp += list[i]
max_l = max(temp, max_l)
max_r = list[n // 2]
temp = 0
for i in range(n//2, n):
temp += list[i]
max_r = max(temp, max_r)
# 返回三个值中的最大值
return max(max_left, max_right, max_l+max_r)
a = [1.5, -12.3, 3.2, -5.5, 23.2, 3.2, -1.4, -12.2, 34.2, 5.4, -7.8, 1.1, -4.9]
print("最大子序和是:", method3(a))
如果不考虑右边界在左边界左边的情况,代码如下:
(自己根据理解写的代码)
# python
def method4(list):
result = list[0]
num = -1
ll = -1 # 第一个大于0的元素下标
for i in range(len(list)):
if list[i] > result:
result = list[i]
num = i
if list[i] > 0:
ll = i
break
else:
print("最大和是一个数:", result)
print("这个数对应的序号为:", i)
temp = list[ll:]
result = 0
temp_sum = 0
lr = -1 # 右边界下标
for i in range(len(temp)):
temp_sum += temp[i]
if temp_sum > result:
result = temp_sum
lr = i + ll
# 反过来从右到左找左边界
rr = -1
for i in range(len(list)-1, -1, -1):
if list[i] > 0:
rr = i
break
temp = list[:rr+1]
result = 0
temp_sum = 0
rl = -1 # 左边界下标
for i in range(len(temp)-1, -1, -1):
temp_sum += temp[i]
if temp_sum > result:
result = temp_sum
rl = i
# 计算总和
result = sum(list[rl: lr+1])
print("区间左边界序号是:", rl+1)
print("区间右边界序号是:", lr+1)
print("最大总和是:", result)
a = [1.5, -12.3, 3.2, -5.5, 23.2, 3.2, -1.4, -12.2, 34.2, 5.4, -7.8, 1.1, -4.9]
method4(a)
如果更改数组为下面这种情况:
# python
b = [1.5, -12.3, 3.2, -5.5, 23.2, 3.2, -1.4, -62.2, 44.2, 5.4, -7.8, 1.1, -4.9]
method4(b)
运算结果如下:
这显然是不对的。所以需要更改代码,书上更改代码部分说“ 如果我们算到某一步时,发现 S ( p , q ) < 0 S(p,q)<0 S(p,q)<0,这时,我们需要从位置 q q q开始,反向计算 M a x b Maxb Maxb”,这里就循环套循环了,复杂度应该是 O ( N 2 ) O(N^2) O(N2)了(实在写不出来这部分代码T.T,得3分已经超出想象了,就这水平还要啥自行车)。
Q1.将例题1.3的线性复杂度算法写成伪代码。(难度系数2颗星)
已经直接写成代码了,伪代码就省略了(偷懒ing)
Q2.在一个数组中寻找一个区间,使得区间内的数字之和等于某个事先给定的数字。
(AB、FB、LK等公司的面试题,后面会解答。(难度系数3颗星))[2]
个人作答(只会 O ( N 2 ) O(N^2) O(N2)的解法):
# python
def method_q2(nums, k):
l = -1
r = -1
for i in range(len(nums)):
temp = 0
for j in range(i, len(nums)):
temp += nums[j]
if temp == k:
l = i
r = j
return [l+1, r+1]
Q3.在一个二维矩阵中,寻找一个矩形的区域,使其中的数字之和达到最大值。
(例题1.3的变种,硅谷公司真实的面试题。(难度系数4颗星))
个人作答:(复杂度 O ( N 4 ) O(N^4) O(N4)…)
# python
def method_q3(nums):
result = 0
l1 = -1
l2 = -1
r1 = -1
r2 = -1
for i in range(len(nums)):
for j in range(len(nums[0])):
s = 0
for m in range(i, len(nums)):
for n in range(j, len(nums[0])):
s += nums[m][n]
if s > result:
result = s
l1 = i
l2 = m
r1 = j
r2 = n
return [nums[i][r1:r2+1] for i in range(l1, l2+1)], result
[1] leetcode 第53题:最大子数组和。 题解里PandaWaKaKa的解法
[2] 这题与leetcode 第560题 和为K的子数组比较相似