初识线段树
问题描述:
给定一个整数数组 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)