最早知道「最大區間和問題」好像是看 Udi Manber 的《算法引論》。最近又在 @吳軍 的《計算之魂》1.3 節中看到。
nums = %w[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].map { |e| e.to_f }
算法
def max_sum_1(ary)
n = ary.size
max_sum = 0
b, e = 0, 0
n.times do |p|
(p...n).each do |q|
# s = ary[p..q].sum
# 爲便於分析算法性能,還是用 for 循環更清晰一些:
s = 0
ary[p..q].each { |e| s += e }
if s > max_sum
max_sum = s
b, e = p, q
end
end
end
[max_sum, [b, e]]
end
p max_sum_1(nums) #=> [52.4, [4, 9]]
算法
def max_sum_2(ary)
n = ary.size
max_sum = 0
b, e = 0, 0
n.times do |p|
partial_sum = 0 # 保存 ary[p..(q - 1)].sum
(p...n).each do |q|
# puts "#{p} .. #{q}"
s = partial_sum + ary[q]
if s > max_sum
max_sum = s
b, e = p, q
end
partial_sum = s
end
end
[max_sum, [b, e]]
end
p max_sum_2(nums)
分治算法
def max_sum_3_using_divide_and_conquer(ary, b, e)
return [ary[b], [b, e]] if b == e
# -----b_l----e_l----------(mid)---------b_r---------e_r-----➡️
mid = (b + e) / 2
max_sum_l, interval = max_sum_3_using_divide_and_conquer(ary, b, mid)
b_l, e_l = interval
max_sum_r, interval = max_sum_3_using_divide_and_conquer(ary, mid + 1, e)
b_r, e_r = interval
if e_l + 1 == b_r
if max_sum_l > 0 and max_sum_r > 0
return [max_sum_l + max_sum_r, [b_l, e_r]]
else
if max_sum_l > max_sum_r
return [max_sum_l, [b_l, e_l]]
else
return [max_sum_r, [b_r, e_r]]
end
end
else
maybe_sum = max_sum_l + ary[(e_l + 1)..(b_r - 1)].sum + max_sum_r
sum2p = {
maybe_sum => [b_l, e_r],
max_sum_l => [b_l, e_l],
max_sum_r => [b_r, e_r]
}
larger_sum = [maybe_sum, max_sum_l, max_sum_r].max
return [larger_sum, sum2p[larger_sum]]
end
end
p max_sum_3_using_divide_and_conquer(nums, 0, nums.size - 1)
終極皇冠👑: 線性算法
@吳軍 說是用「正、反兩遍掃描」,但理解起來太費勁。其實用遞歸也能解釋(先不考慮邊界情況):
- 找到第一個爲正
+
的數 ,再找 之後第一個爲負-
的數 。計算出 ; - 遞歸尋找 段上的最大區間和,記爲 ,同時順道求出 ;
- 剩下的就是常規操作了:
,意味着排除掉 ,需要在 中選擇。
,意味着只需在 中選擇。
不過坑🕳️還是很多的,昨晚調試到凌晨兩點🕑😖。
def find_the_first_positive_num(ary, b = 0)
l = nil
sum_before_l = 0
ary[b..].each_with_index do |e, i|
if e >= 0
l = b + i
break
end
sum_before_l += e
end
if l.nil? # 全爲負。
return [nil, nil]
else
return [l, sum_before_l]
end
end
def find_the_first_negative_num(ary, b = 0)
l = nil
sum_before_l = 0
ary[b..].each_with_index do |e, i|
if e < 0
l = b + i
break
end
sum_before_l += e
end
if l.nil? # 全爲正。
return [nil, nil]
else
return [l, sum_before_l]
end
end
def max_sum_4(ary, b, e, depth = 0)
# return format: [sum_before_l, [l..r], max_sum]
return [0, [b, e], ary[b]] if b == e if b == e
p0, sum_left_side_0 = find_the_first_positive_num(ary, b)
if p0.nil? # 全爲負數。
max_negative = ary[b..e].max
idx = ary.index(max_negative)
return [0, [idx, idx], max_negative]
else
l, r, max_sum = nil
q0, sum_p0 = find_the_first_negative_num(ary, p0)
if q0.nil?
return [ary[b...p0].sum, [p0, e], ary[p0..e].sum] if q0.nil? # [p0 .. e] 全爲正數。
else
sum_left_side, interval, max_sum_rest = max_sum_4(ary, q0, e, depth)
l_rest, r_rest = interval
return [sum_left_side_0, [p0, q0 - 1], sum_p0] if max_sum_rest < 0
if sum_p0 + sum_left_side >= 0
if sum_left_side + max_sum_rest >= 0
# 擴展至 [p0 .. r_rest]
return [sum_left_side_0, [p0, r_rest], sum_p0 + sum_left_side + max_sum_rest]
else
# 只保留 [p0 .. q0 - 1]
return [sum_left_side_0, [p0, q0 - 1], sum_p0]
end
else
if sum_p0 >= max_sum_rest
return [sum_left_side_0, [p0, q0 - 1], sum_p0]
else
return [sum_left_side_0 + sum_p0 + sum_left_side, [l_rest, r_rest], max_sum_rest]
end
end
end
end
end
p max_sum_4(nums, 0, nums.size - 1)