初識線段樹<leetcode筆記>
技術標籤:leetcode資料結構演算法javaleetcode動態規劃
初識線段樹
問題描述:
給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。
我的思路:
列舉陣列中所有的連續子陣列,找出所有連續子陣列和的最大值。
缺點:
時間複雜度較高o(n^2);
class Solution{
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
for(int i = 0;i < nums.length;i++){
int sum = 0;
for(int j = i;j < nums.length;j++){
sum += nums[j];
if(sum > max){
max = sum;
}
}
}
return max;
}
}
動態規劃:
f(i)的求法:
考慮nums[i]應加入f(i-1)對應的那一段還是單獨成一段。這取決於nums[i]+f(i-1)和num[i]的大小,二者取其大。
f(i) = max{nums[i]+f(i-1),nums[i]};
優點:
相比於暴力列舉,時間複雜度由o(n^2)降為o(n),空間複雜度為o(1).
class Solution {
public int maxSubArray(int[] nums) {
int max = Integer.MIN_VALUE;
int dp = nums[0];
int result = dp;
for(int i = 1;i < nums.length;i++){
dp = Math. max(dp + nums[i],nums[i]);
result = Math.max(result,dp);
}
return result;
}
}
接下來便是本文的重點初識線段樹
分治法(線段樹):
整體思路:
定義操作:getInfo(nums,0,nums.length - 1);如何實現分治呢?我們取mid = (left+right)>>1;對[left,mid]和[mid+1,right]分治。當區間長度縮為1時開始遞歸回升。
需要維護的資訊:
*lSum表示[l,r]內以l為左端點的最大子段和
*rSum表示[l,r]內以r為右端點的最大子段和
*mSum表示[l,r]內的最大子段和
*iSum表示[l,r]的區間和
遞歸回升時資訊合併:
*iSum:左子區間的iSum和右子區間的iSum之和
*lSum:要麼等於左子區間的lSum,或者左子區間的iSum加上右子區間的 lSum,二者取大。
*rSum:要麼等於右子區間的rSum,或者右子區間的iSum加上左子區間的 rSum,二者取大。
*mSum:左子區間的mSum,右子區間的mSum,左子區間的rSum加上右子區間的lSum,三者取大。(考慮最大子段是否跨越m)
class Solution {
class Status{
public int lSum,rSum,mSum,iSum;
public Status(int lSum,int rSum,int mSum,int iSum){
this.lSum = lSum;
this.rSum = rSum;
this.mSum = mSum;
this.iSum = iSum;
}
}
public int maxSubArray(int[] nums) {
return getInfo(nums,0,nums.length - 1).mSum;
}
public Status getInfo(int[] nums,int left,int right){
if(left == right) return new Status(nums[left],nums[left],nums[left],nums[left]);
int mid = (left + right) >> 1;
Status lSub = getInfo(nums,left,mid);
Status rSub = getInfo(nums,mid + 1,right);
return pushUp(lSub,rSub);
}
public Status pushUp(Status lSub,Status rSub){
int iSum = lSub.iSum + rSub.iSum;
int lSum = Math.max(lSub.lSum,lSub.iSum + rSub.lSum);
int rSum = Math.max(rSub.rSum,rSub.iSum + lSub.rSum);
int mSum = Math.max(Math.max(lSub.mSum,rSub.mSum),lSub.rSum + rSub.lSum);
return new Status(lSum,rSum,mSum,iSum);
}
}
優點:
可以用於解決任意的子區間,而不僅限於[0,nums.length - 1], 如果把分治下去出現的所有子區間的資訊都用堆式儲存的方式記憶化下來,即建成一顆真正的樹之後,我們就可以在o(logn)的時間內求到任意區間內的答案,我們甚至可以修改序列中的值,做一些簡單的維護,之後仍然可以在o(logn)的時間內求到任意區間內的答案,對於大規模查詢的情況下,這種方法的優勢便體現了出來。這棵樹就是上文提及的一種神奇的資料結構——線段樹。
時間複雜度: o(n)
空間複雜度: 遞迴使用o(logn)