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);    //更新父节点
   }
}
posted @ 2020-07-10 13:50  ganshuoos  阅读(149)  评论(0编辑  收藏  举报