• leetcode刷题笔记五十三 最大子序和


    leetcode刷题笔记五十三 最大子序和

    源地址:53. 最大子序和

    问题描述:

    给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    示例:

    输入: [-2,1,-3,4,-1,2,1,-5,4],
    输出: 6
    解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
    进阶:

    如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

    代码补充:

    //通过观察,易发现本题可以通过动态规划解题
    //初始状态: maxSum(0) = nums(0)
    //状态转换方程: maxSum(i) = math.max(maxSum(i-1)+nums(i), nums(i))
    //为了节省空间,使用nums记忆其对应的maxSum
    //时间复杂度:O(n) 空间复杂度:O(1)
    object Solution {
        def maxSubArray(nums: Array[Int]): Int = {
            val length = nums.length
            if(length == 0) return 0
            for(i <- 1 until length){
                nums(i) = math.max(nums(i-1)+nums(i), nums(i))
            }
            return nums.max 
        }
    }
    
    /**
    分治法参考了官方题解,其中提到了线段树数据结构
    官方题解:https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-by-leetcode-solution/
    将问题get(arr, left, right)的问题划分为get(arr, left, mid)
    get(arr, mid+1, right) 再对两个子问题的结果进行合并
    
    针对[l, r]区间,需要维护4个量
    这四个量的计算都是基于区间位置是否位于子区间,是否跨越两区间计算
    lSum表示[l,r]内以l为左端点的最大子段和
    rSum表示[l,r]内以r为右端点的最大字段和
    mSum表示[l,r]内的最大子段和
    iSum表示[l,r]区间和
    
    iSum = lMerv.iSum + rMerv.iSum
    lSum = math.max(lMerv.lSum, lMerv.iSum+rMerv.lSum)
    rSum = math.max(rMerv.rSum, rMerv.iSum+lMerv.rSum)
    mSum = math.max(math.max(lMerv.mSum, rMerv.mSum), lMerv.rSum+rMerv.lSum)
    
    时间复杂度:O(logn) ---> O(n) 空间复杂度:O(logn)
    */
    object Solution {
        def maxSubArray(nums: Array[Int]): Int = {
            class mervTree(var iSum:Int, var lSum:Int, var rSum:Int, var mSum:Int)
    
            def pushUp(lMerv: mervTree, rMerv: mervTree): mervTree = {
                val iSum = lMerv.iSum + rMerv.iSum
                val lSum = math.max(lMerv.lSum, lMerv.iSum+rMerv.lSum)
                val rSum = math.max(rMerv.rSum, rMerv.iSum+lMerv.rSum)
                val mSum = math.max(math.max(lMerv.mSum, rMerv.mSum), lMerv.rSum+rMerv.lSum)
                return new mervTree(iSum, lSum, rSum, mSum)
            }
    
            def get(nums: Array[Int], l: Int, r: Int) : mervTree = {
                if (l == r) return new mervTree(nums(l), nums(l), nums(l), nums(l))
                val m = (l + r)/2
                val lPush = get(nums, l, m)
                val rPush = get(nums, m+1, r)
                return pushUp(lPush, rPush)
            }
    
            val length = nums.length
            return  get(nums, 0, length-1).mSum 
        }
    }
    

    知识补充:

    线段树:

    线段树内容参考:https://www.cnblogs.com/xenny/p/9801703.htmlhttps://www.cnblogs.com/jason2003/p/9676729.html

    线段树概念,以本题为例,如图所示 为一种特殊二叉树

    递归建树:

    inline void build(int i,int l,int r){//递归建树
       tree[i].l=l;tree[i].r=r;
       if(l==r){//如果这个节点是叶子节点
           tree[i].sum=input[l];
           return ;
       }
       int mid=(l+r)>>1;
       build(i*2,l,mid);//分别构造左子树和右子树
       build(i*2+1,mid+1,r);
       tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//刚才我们发现的性质return ;
    }
    

    线段树的查询方法:

    1. 如果这个区间被包含在目标区间里面,直接返回这个区间的值
    2. 如果这个区间与目标区间毫不相干, 返回0
    3. 如果这个区间的左儿子和目标区间有交集,搜索左儿子
    4. 如果这个区间的右儿子和目标区间有交集,搜索右儿子
    inline int search(int i,int l,int r){
       if(tree[i].l>=l && tree[i].r<=r)//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
           return tree[i].sum;
       if(tree[i].r<l || tree[i].l>r)  return 0;//如果这个区间和目标区间毫不相干,返回0
       int s=0;
       if(tree[i*2].r>=l)  s+=search(i*2,l,r);//如果这个区间的左儿子和目标区间又交集,那么搜索左儿子
       if(tree[i*2+1].l<=r)  s+=search(i*2+1,l,r);//如果这个区间的右儿子和目标区间又交集,那么搜索右儿子
       return s;
    }
    

    线段树的区间更新与lazytag

    lazytag
      线段树在进行区间更新的时候,为了提高更新的效率,所以每次更新只更新到更新区间完全覆盖线段树结点区间为止,这样就会导致被更新结点的子孙结点的区间得不到需要更新的信息,所以在被更新结点上打上一个标记,称为lazytag,等到下次访问这个结点的子结点时再将这个标记传递给子结点,所以也可以叫延迟标记。递归更新的过程,更新到结点区间为需要更新的区间的真子集不再往下更新,下次若是遇到需要用这下面的结点的信息,再去更新这些结点,复杂度为O(logn)。

    void Pushdown(int k){    //更新子树的lazy值,这里是RMQ的函数,要实现区间和等则需要修改函数内容
       if(lazy[k]){    //如果有lazy标记
           lazy[k<<1] += lazy[k];    //更新左子树的lazy值
           lazy[k<<1|1] += lazy[k];    //更新右子树的lazy值
           t[k<<1] += lazy[k];        //左子树的最值加上lazy值
           t[k<<1|1] += lazy[k];    //右子树的最值加上lazy值
           lazy[k] = 0;    //lazy值归0
       }
    }
    
    //递归更新区间 updata(L,R,v,1,n,1);
    void updata(int L,int R,int v,int l,int r,int k){    //[L,R]即为要更新的区间,l,r为结点区间,k为结点下标
       if(L <= l && r <= R){    //如果当前结点的区间真包含于要更新的区间内
           lazy[k] += v;    //懒惰标记
           t[k] += v;    //最大值加上v之后,此区间的最大值也肯定是加v
       }
       else{
           Pushdown(k);    //重难点,查询lazy标记,更新子树
           int m = l + ((r-l)>>1);
           if(L <= m)    //如果左子树和需要更新的区间交集非空
               update(L,R,v,l,m,k<<1);
           if(m < R)    //如果右子树和需要更新的区间交集非空
               update(L,R,v,m+1,r,k<<1|1);
           Pushup(k);    //更新父节点
       }
    }
    
  • 相关阅读:
    实现手机页面转换
    Activity到另一个Acivity
    Android简单的Button事件处理
    样式定义Android界面样式
    更改手机窗口画面底色——color.xml定义颜色
    搭建每日构建环境
    perl常用语法
    perl开发文档
    IT项目实施规范
    快速开发php扩展
  • 原文地址:https://www.cnblogs.com/ganshuoos/p/13278843.html
Copyright © 2020-2023  润新知