JavaScript刷LeetCode拿offer-分治

前言

今天没啥前言,分治很难,主要难在如何拆分后比较好治理合并,这比二分这些只要拆了就结束要难上一个 level,所以这里属于出入 分治 这种想法的思维,后续会尽可能的锻炼这样的做法;做一道分治,如果能用其他方法代替的时候,一般分治不算是最优解,起码很伤脑子;

正文

概念

分治即分而治之,所以要分成两部分

  • 分:将一个规模为 N 的问题分解为若干个规模较小的子问题
  • 治:根据子问题的解求原问题

关键点

  • 一定是先分再治
  • 治一定是利用分的结果进行的,也就是说治依赖于分

适用场景

  1. 如果问题可以被分解为若干个规模较小的相同问题
  2. 这些被分解的问题的结果可以进行合并
  3. 这些被分解的问题是相互独立的,不包含重叠的子问题

分支和 dp 有很深联系,且与二分法也有关联,本质上,二分就是一直只有分没有治的分治,因为二分的结果只需要找到那个较小的相同问题的解,不需要再合并起来;

技巧

  1. 思考子问题的求解边界,使用函数来定义问题
  2. 思考如何将子问题的解进行合并 – 假设子问题已经计算好了,如何合并起来
  3. 思考编码思路 – 一般使用递归

分治和二分,dp的异同

  • 二分只对问题进行分,分完直接舍弃;而分治不仅需要对问题进行分解,还需要对多个问题进行治理
  • 分治和 dp 都涉及到问题的问题,并且都需要保证子问题的不重不漏。
    • dp 是通过递推和选择进行转译,从特殊推广到一半
    • 分治也可能涉及到选择;
    • dp 解决的问题往往伴随重叠子问题,而分治则不是

小结

  • 如果一个问题被文杰为若干个不重叠的独立子问题,并且子问题可以推到出原问题,我们就可以用分治来解决;

题目

53. 最大子序和

分析 – 分治法

  1. 先分 – 运用递归的方法将数组区间的左右节点 l,r 不断二分出去,直到 l === r 为止,这个时候需要考虑怎么治理了
  2. 再治 – 这里最终要求的是最大的连续子序列,我们先考虑两个值合并,最大的情况是三种, Math.max(L,R,L+R),
    • 但是当再多一点值的时候,我们就需要改变一下 Math.max(LMAX,RMAX,L_Rmax+R_Lmax) 这里的 LMAX, RMAX 是指合并两个区间的最大值,L_Rmax 是指在 L 区间包含 right 终点为最大区间;
  3. 所以治的过程中,每个区间需要有4个变量,分别是 totalSum 区间总和,leftSum 包含 left 节点的最大连续子列和, rightSum 包含 right 节点的最大连续子列和, maxSum 区间的最大值
  4. 初始化的时候,也就是单个节点的时候,4个变量都是唯一值 nums[l]
  5. 开始合并治理,
    • 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)
  6. 所以先递后归,时间复杂度为 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
}

分析 – 贪心

  1. 求的是最大和的连续子数组
  2. 用 sum 缓存前面和大于 0 的子数组之和,一旦小于 0 ,就不再累加,重新置 0, 保持每一次迭代前 sum 的值都是 >=0
  3. 这样对于每一个局部子数组,它的累加值都是大于等于 0 的,这样每次累加一个新值,就进行最大值比较,保证整体是一个最大子数组之和
  4. 时间复杂度 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=<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值