分治算法的主要思想是将原问题递归地分成若干个子问题,直到子问题满足边界条件,停止递归。将子问题逐个击破(一般是同种方法),将已经解决的子问题合并,最后,算法会层层合并得到原问题的答案。
- 分:递归地将问题分解为各个的子问题(性质相同的、相互独立的子问题);
- 治:将这些规模更小的子问题逐个击破;
- 合:将已解决的子问题逐层合并,最终得出原问题的解;
- 原问题的计算复杂度随着问题的规模的增加而增加。
- 原问题能够被分解成更小的子问题。
- 子问题的结构和性质与原问题一样,并且相互独立,子问题之间不包含公共的子子问题。
- 原问题分解出的子问题的解可以合并为该问题的解。
注意使用分治算法其中一个要求是,子问题合并的代价不能太大,否则就起不了降低时间复杂度的效果了。
例题:leetcode 53
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大为6。
我们定义一个操作 get(a, l, r) 表示查询 aa 序列 [l, r][l,r] 区间内的最大子段和,那么最终我们要求的答案就是 get(nums, 0, nums.size() - 1)。如何分治实现这个操作呢?对于一个区间 [l, r][l,r],我们取 m =(l+r)/2,对区间 [l, m][l,m] 和 [m + 1, r][m+1,r] 分治求解。当递归逐层深入直到区间长度缩小为 11 的时候,递归「开始回升」。这个时候我们考虑如何通过 [l, m][l,m] 区间的信息和 [m + 1, r][m+1,r] 区间的信息合并成区间 [l, r][l,r] 的信息。最关键的两个问题是:
我们要维护区间的哪些信息呢? 我们如何合并这些信息呢? 对于一个区间 [l, r][l,r],我们可以维护四个量:
lSum 表示 [l, r][l,r] 内以 ll 为左端点的最大子段和 rSum 表示 [l, r][l,r] 内以 rr 为右端点的最大子段和 mSum 表示 [l, r][l,r] 内的最大子段和 iSum 表示 [l, r][l,r] 的区间和 以下简称 [l, m][l,m] 为 [l, r][l,r] 的「左子区间」,[m + 1, r][m+1,r] 为 [l, r][l,r] 的「右子区间」。我们考虑如何维护这些量呢(如何通过左右子区间的信息合并得到 [l, r][l,r] 的信息)?对于长度为 11 的区间 [i, i][i,i],四个量的值都和 a_ia
相等。对于长度大于 11 的区间:
首先最好维护的是 iSum,区间 [l, r][l,r] 的 iSum 就等于「左子区间」的 iSum 加上「右子区间」的 iSum。 对于 [l, r][l,r] 的 lSum,存在两种可能,它要么等于「左子区间」的 lSum,要么等于「左子区间」的 iSum 加上「右子区间」的 lSum,二者取大。 对于 [l, r][l,r] 的 rSum,同理,它要么等于「右子区间」的 rSum,要么等于「右子区间」的 iSum 加上「左子区间」的 rSum,二者取大。 当计算好上面的三个量之后,就很好计算 [l, r][l,r] 的 mSum 了。我们可以考虑 [l, r][l,r] 的 mSum 对应的区间是否跨越 mm——它可能不跨越 mm,也就是说 [l, r][l,r] 的 mSum 可能是「左子区间」的 mSum 和 「右子区间」的 mSum 中的一个;它也可能跨越 mm,可能是「左子区间」的 rSum 和 「右子区间」的 lSum 求和。三者取大。 这样问题就得到了解决。