基本认识
分治法,字面意思是“分而治之”,就是把一个复杂的一个问题分成两个或多个相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单地直接求解,原问题的解即子问题的解的合并,这个思想是很多高效算法的基础,例如排序算法(快速排序,归并排序),傅里叶变换(快速傅里叶变换)等。
基本思想与原理
将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
对于一个规模为n的问题,若该问题可以容易的解决(比如规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解决这些子问题,然后将各个子问题的解合并得到原问题的解。
适用的问题
分治算法适用的问题应满足以下几个条件:
(1)该问题的规模缩小到一定程度就可以很容易解决:
这个条件非常必要,当问题的数据量也就是规模一定小的时候,问题的复杂度要足够低。
(2)原问题可以分解为多个性质相同的子问题,这里注意是最优子结构性质:
这些子问题与原问题相比,只是问题的规模有所降低,其结构和求解方法与原问题相同或相似。
(3)利用该问题分解出的子问题的解可以合并为该问题的解:
单单分解为小问题之后还不能算完成,必须要能够将小问题的解合并为这个问题的最终解才能算真正用到了分治的思想。
(4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共子问题:
这一条也是最关键的,各个子问题之间必须要保证独立性,即不互相影 响。如果相互之间有影响,这时候我们采用的是动态规划就更加好一点。
求解的步骤与模板
(1)分解:
将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
(2)处理:
若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题。
(3)合并:
将各个子问题的解合并为原问题的解。
引例部分
找出伪币问题:
给你一个装有16个硬币的袋子。16个硬币中最多有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。你的任务是找出这个伪造的硬币。为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。请用尽量少的比较次数来判断伪币的存在并找出这一伪币。
解题思路:
利用分而治之方法,假如把16硬币的例子看成一个大的问题。第一步,把这一问题分成两个小问题。随机选择8个硬币作为第一组称为A组,剩下的8个硬币作为第二组称为B组。这样,就把16个硬币的问题分成两个8硬币的问题来解决。第二步,判断A和B组中是否有伪币。可以利用仪器来比较A组硬币和B组硬币的重量。假如两组硬币重量相等,则可以判断伪币不存在。假如两组硬币重量不相等,则存在伪币,并且可以判断它位于较轻的那一组硬币中。接下来,把两个或三个硬币的情况作为不可再分的小问题,在一个小问题中,通过将一个硬币分别与其他两个硬币比较,最多比较两次就可以找到伪币。这样,16硬币的问题就被分为两个8硬币(A组和B组)的问题。通过比较这两组硬币的重量,可以判断伪币是否存在。如果没有伪币,则算法终止。否则,继续划分这两组硬币来寻找伪币。假设B是轻的那一组,因此再把它分成两组,每组有4个硬币。称其中一组为B1,另一组为B2。比较这两组,肯定有一组轻一些。如果B1轻,则伪币在B1中,再将B1又分成两组,每组有两个硬币,称其中一组为B1a,另一组为B1b。比较这两组,可以得到一个较轻的组。由于这个组只有两个硬币,因此不必再细分。比较组中两个硬币的重量,可以立即知道哪一个硬币轻一些。较轻的硬币就是所要找的伪币。
实战部分
最大子序和问题:
解题思路:
分治法的思路是这样的,连续子序列的最大和主要由这三部分子区间里元素的最大和得到:
第 1 部分:子区间 [left, mid];
第 2 部分:子区间 [mid + 1, right];
第 3 部分:包含子区间 [mid , mid + 1] 的子区间,通过递归的操作,对它们三者求最大值即可。
第一步(分解):
因为最大子序和以领来自于以上三个区间,我们可以将这个大问题分解成三个小的子问题:求左区间最大子序和、求右区间最大子序和、求包含左区间和右区间的子序和。
第二步(处理):
对于左区间和右区间,直接通过递归求单个区间最大子序和即可;求完左区间和右区间后,再求包含左区间和右区间的子序和,不断更新最大值即可。
第三步(合并):
输出最顶层递归函数的返回值即可。
结合下面的图解和代码中的注释很容易明白。
下面附上Python3的题解代码
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
l=len(nums)
if l <= 1:
return 0 if l==0 else nums[0]
else:
#递归计算左半边最大子序和
max_left = self.maxSubArray(nums[0:l//2])
#递归计算右半边最大子序和
max_right = self.maxSubArray(nums[l//2:l])
#计算中间的最大子序和,从右到左计算左边的最大子序和,从左到右计算右边的最大子序和,再相加
max_l = nums[l//2 -1]
tmp = 0
for i in range(l//2 -1,-1,-1):
tmp += nums[i]
max_l = max(tmp, max_l)
max_r = nums[l//2]
tmp = 0
for i in range(l//2,l):
tmp += nums[i]
max_r = max(tmp, max_r)
#返回三个中的最大值
return max(max_right,max_left,max_l+max_r)
趁热打铁 刷题练习部分(持续更新)
以下是LeetCode题库中一些用到分治算法的经典例题的题目及解析,有题干,有题解代码、有解题思路(持续更新):
No.23.合并K个排序链表:
https://www.jianshu.com/p/862bc7144f80
No.53.最大子序和:
https://blog.csdn.net/LanXiu_/article/details/104216342