1. 程式人生 > 實用技巧 >leetcode刷題筆記五十三 最大子序和

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);    //更新父節點
   }
}