题目:求最大子数组
- 在一个数组中找到和最大的子数组。
- 数组中有正有负,但都是
integer
类型。
题目来源:算法导论第38页 4.1 最大子数组问题
分析:
根据书中的问题,采用分治思想来解题。具体可参考算法导论。
思路:
- 将整个数组一分为二,每次分完之后做四件事件:
- 查看是否是最小数组即长度是1,因为数组长度是1那么总和就是这一个数的值。
- 如果不是1,那就分别递归进左边的数组和右边的数组继续分,直到分到1再返回。
- 每次递归除了左边的最大子数组和右边的最大子数组外,还有交叉在中间的最大子数组。所以要把中间的最大子数组也算出来。
- 算出三者的各自的和将和最大的那个子数组并返回,同时返回它的两端下标。
- 每次递归都会返回左边,右边,中间的最大的那个子数组。
- 最后一次递归返回最大的子数组,答案就出来了。
代码:
class FindMaxNum:
def _find_crossing(self, A, low, mid, high): # 寻找穿过中间值的最大子数组
leftSum = float('-inf')
ans = 0
maxLeft = mid
maxRight = mid + 1
for i in range(mid, low - 1, -1): # 找到左边的最大子数组
ans += A[i]
if ans > leftSum:
leftSum = ans
maxLeft = i
rightSum = float('-inf')
ans = 0
for j in range(mid + 1, high + 1): # 找到右边的最大子数组
ans += A[j]
if ans > rightSum:
rightSum = ans
maxRight = j
return maxLeft, maxRight, leftSum + rightSum # 返回最大的子数组以及它的两端下标
def find_max_num(self, A, low, high):
if low == high: # 如果分到了1,那么就返回
return low, high, A[low]
else:
mid = (low + high) // 2 # 将数组一分为二
left_low, left_high, left_sum = self.find_max_num(A, low, mid) # 递归进左子数组
right_low, right_high, right_sum = self.find_max_num(A, mid + 1, high) # 递归进右子数组
cro_low, cro_high, cro_sum = self._find_crossing(A, low, mid, high) # 寻找中间子数组
if left_sum >= right_sum and left_sum >= cro_sum: # 左边最大返回左边
return left_low, left_high, left_sum
elif right_sum >= left_sum and right_sum >= cro_sum: # 右边最大返回右边
return right_low, right_high, right_sum
else: # 否则中间最大返回中间
return cro_low, cro_high, cro_sum
if __name__ == '__main__': # 测试通过
test = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]
fn = FindMaxNum()
i, j, res = fn.find_max_num(test, 0, len(test) - 1)
print(test[i:j+1])
print(i,j)
print(res)
复杂度分析:
- 时间复杂度:O(nlogn) n 为数组的长度
- 空间复杂度:O(1) 完全没有开辟新的数组空间,仅仅是在原数组上分治。
总结:
- 分治算法可以大幅度提高运行效率,相比于暴力法,速度提升很多。
- 算法导论上的这个分治法应该是我看到的最好的分治法了,很多地方的算法解这道题时总是会开辟新的数组,这就导致了内存空间的浪费,如果数据量多的话,很容易空间不够。
- 分治的主要思想就是找到递归条件和线性条件。然后用分而治之的方法从线性条件不停地向前合并,最终找到答案。主要的原理和递归很像。