前言
今天没啥前言,分治很难,主要难在如何拆分后比较好治理合并,这比二分这些只要拆了就结束要难上一个 level,所以这里属于出入 分治
这种想法的思维,后续会尽可能的锻炼这样的做法;做一道分治,如果能用其他方法代替的时候,一般分治不算是最优解,起码很伤脑子;
正文
概念
分治即分而治之
,所以要分成两部分
- 分:将一个规模为 N 的问题分解为若干个规模较小的子问题
- 治:根据子问题的解求原问题
关键点
- 一定是先分再治
- 治一定是利用分的结果进行的,也就是说治依赖于分
适用场景
- 如果问题可以被分解为若干个规模较小的相同问题
- 这些被分解的问题的结果可以进行合并
- 这些被分解的问题是相互独立的,不包含重叠的子问题
分支和 dp 有很深联系,且与二分法也有关联,本质上,二分就是一直只有分没有治的分治,因为二分的结果只需要找到那个较小的相同问题的解,不需要再合并起来;
技巧
- 思考子问题的求解边界,使用函数来定义问题
- 思考如何将子问题的解进行合并 – 假设子问题已经计算好了,如何合并起来
- 思考编码思路 – 一般使用递归
分治和二分,dp的异同
- 二分只对问题进行分,分完直接舍弃;而分治不仅需要对问题进行分解,还需要对多个问题进行治理
- 分治和 dp 都涉及到问题的问题,并且都需要保证子问题的不重不漏。
- dp 是通过递推和选择进行转译,从特殊推广到一半
- 分治也可能涉及到选择;
- dp 解决的问题往往伴随重叠子问题,而分治则不是
小结
- 如果一个问题被文杰为若干个不重叠的独立子问题,并且子问题可以推到出原问题,我们就可以用分治来解决;
题目
53. 最大子序和
分析 – 分治法
- 先分 – 运用递归的方法将数组区间的左右节点 l,r 不断二分出去,直到 l === r 为止,这个时候需要考虑怎么治理了
- 再治 – 这里最终要求的是最大的连续子序列,我们先考虑两个值合并,最大的情况是三种, Math.max(L,R,L+R),
- 但是当再多一点值的时候,我们就需要改变一下 Math.max(LMAX,RMAX,L_Rmax+R_Lmax) 这里的 LMAX, RMAX 是指合并两个区间的最大值,L_Rmax 是指在 L 区间包含 right 终点为最大区间;
- 所以治的过程中,每个区间需要有4个变量,分别是 totalSum 区间总和,leftSum 包含 left 节点的最大连续子列和, rightSum 包含 right 节点的最大连续子列和, maxSum 区间的最大值
- 初始化的时候,也就是单个节点的时候,4个变量都是唯一值 nums[l]
- 开始合并治理,
- totalSum 直接将两个节点的 totalSum 合并即可;
- leftSum 总是和 left 区间相关 – Math.max(left.maxSum,left.totalSum+right.leftSum), 要不直接区左区间的最大值,要不全取左区间 + 右区间的 leftSum
- 同理 rightSum 也总是和 right 区间相关 – Math.max(right.maxSum,right.totalSum+left.rightSum)
- maxSum 分三种情况 – Math.max(left.maxSum,right.maxSum,left.rightSum+right.leftSum)
- 所以先递后归,时间复杂度为 O(n)
var maxSubArray = function (nums) {
const recursion = (l,r) => {
if(l === r) {
return {
totalSum: nums[l],
leftSum: nums[l],
rightSum: nums[l],
maxSum: nums[l]
}
}
const mid = ((r-l)>>2)+l
const left = recursion(l,mid)
const right = recursion(mid+1,r)
return {
totalSum:left.totalSum+right.totalSum, // 区间内值的总和
leftSum:Math.max(left.leftSum,left.totalSum+right.leftSum), // 左边界开始的最大连续子列和
rightSum: Math.max(right.rightSum,right.totalSum+left.rightSum), // 区间哟偶边界结束的最大连续子列和
maxSum:Math.max(left.maxSum,right.maxSum,left.rightSum+right.leftSum)
}
}
return recursion(0,nums.length-1).maxSum
}
分析 – 贪心
- 求的是最大和的
连续子数组
- 用 sum 缓存前面和大于 0 的子数组之和,一旦小于 0 ,就不再累加,重新置 0, 保持每一次迭代前 sum 的值都是 >=0
- 这样对于每一个局部子数组,它的累加值都是大于等于 0 的,这样每次累加一个新值,就进行最大值比较,保证整体是一个最大子数组之和
- 时间复杂度 O(n)
var maxSubArray = function (nums) {
let max = -Infinity;
let sum = 0
for(let i = 0 ;i<nums.length;i++){
sum+=nums[i]
max = Math.max(sum,max)
if(sum<=0){
sum=<