左神进阶班笔记Part4:BBST、累加和为定值最长子数组问题

平衡搜索二叉树BBST

AVL树

任何一个节点的左子树和右子树高度差不大于1,复杂度还是O(logN)。导致调整非常频繁。

红黑树

每个节点染上色,头和叶节点必然黑,相邻两个节点不能出现连续的红节点,任何一条从根到叶节点的路径上黑节点数相等。所以最长链和最短链的高度差不超过1倍。

应用

C++中map底层使用的就是红黑树,自动排序。
map.rbegin()获取最大数,
map.lower_bound(k)返回迭代器,指向第一个key不小于k的元素,
map.upper_bound(k)返回迭代器,指向第一个key大于k的元素,
map.equal_nound(k)返回一个迭代器pair,表示关键词等于k的元素范围,若k不错在,则pair两个成员均等于map.end()。

SB树

每个结点所在子树的结点个数不小于其兄弟的两个孩子所在子树的结点个数。
在这里插入图片描述

例题

【一】
在这里插入图片描述
在这里插入图片描述

累加和为定值的最长数组

利用前缀和

【一】
给定一个数组arr,数组中有0,正值和负值,给定一个aim值,求累加和为给定值的最长子数组。
【TIP】连续的子数组、子串问题,求出以每个位置数 i 截止的最长子数组,最长的子数组必定在其中。
【思路】
1.使用sum表示从0开始累加到当前位置 i 的所有数目的和,在以当前位置 i 结尾时的子数组中 ,找到从起始位置开始累加 结果为sum-aim的最早位置k ,则此时的数组(k,i] 为以当前位置结尾的最长子数组。
2.用map<sum,i>记录起始位置到任意位置的累加和sum,但相同的累加和,只需记录最早出现的下标。
3.注意!!! -1位置的累加和为0,一定要加上。 否则会漏掉从第一个数开始的子数组。

public static int maxLength(int[] arr,int aim){
	if(arr == null || arr.length == 0){
		return 0;
	}
	HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
	map.put(0,-1);
	int len = 0;
	int sum = 0;
	for(int i = 0;i< arr.length; i++){
		sum += arr[i];
		if(map.containsKey(sum-aim)){
			len = Math.max(i-map.get(sum-aim),len);
		}
		if(!map.containsKey(sum)){ //记录过的sum不需要再次记录,只需要记录最早的
			map.put(sum,i);
		}
	}
	return len;
}

【二】
给定一个数组,求数组中奇数和偶数数目相等的最长子数组。

【思路】将奇数变成1,偶数变成-1,使得aim等于0,就转变为了上一题。

【三】
给定一个只有0,1,2三种元素的数组,求数组中1和2个数相等的最长子数组。
【思路】0,1不变,2改为-1,同上。

【四】
在这里插入图片描述【思路】
在这里插入图片描述

public static int mostEOR(int[] arr){
	int ans = 0;
	int xor = 0;
	int[] mosts = new int[arr.length];
	HashMap<Integer,Integer> map = new HashMap<>();
	map.put(0,-1);
	for(int i = 0;i<arr.length;i++){
		xor ^= arr[i];
		if(map.containsKey(xor)){
			int pre = map.get(xor);
			most[i] = pre == -1 ? 1: (mosts[pre] +1);
		}
		if(i >0){
			mosts[i] = Math.max(mosts[i-1],mosts[i]);
		}
		map.put(xor,i);
		ans = Math.max(ans,mosts[i]);
	}
	return ans;
}

扩展

【五】
在这里插入图片描述
【思路】双指针,如果当前LR指针区间中的数累加和小于等于当前值,就向右方扩展,如果大于则将左边的数值去掉。
之所以可以采用双指针在于数组中的数据全部都是正数,则指针增加则会增加,左指针增加则数据减少。

public static int getMaxLength(int[] arr,int k){
	if(arr == null || arr.length == null || k<0){
		return 0;
	}
	int L = 0;
	int R = 0;
	int sum = arr[0];
	int len = 0;
	while(R < arr.length){
		if(sum == k){
			len = Math.max(len,R-L+1);
			sum -= arr[L++];
		}else if(sum < k){
			R ++;
			if(R == arr.length){
				break;
			}
			sum += arr[R];
		}else{
			sum -= arr[L++];
		}
	}
	return len;
}

【六】
在这里插入图片描述【思路】新建两个数组,第一个数组sums保存当前位置往右开始求得的最小的累加和的值,第二个数组ends保存sums数组求得的最小累加和对应的区间结束的位置(下标)。从右往左逐个求这两个数组,如果 i 右面的累加和是负数,则证明有利可图,可以减少当前值,则当前值加上右方的最小累加和,并且将右方保存的最后位置赋值到当前位置,从而可以快速求出最小累加和。
此时求解当前位置的最小累加和的时候就可以利用刚才形成的两个数组进行求解,对于是否包含当前位置会直接包含当前的最小累加和,并且此时的位置会直接指到求出最小累加和最右方的位置,并且再次向后方进行计算。如果当前累加和符合,但是加上新的元素不行时,则此时按照双指针的形式将左边界右移 看能否将新的元素加入。
【时间复杂度】O(n),因为两个指针不会回溯。左神书中O(nlgn)解答非最优解。

public static int maxLengthAwesome(int[] arr,int k){
	if(arr ==null || arr.length == 0){
		return 0;
	}
	int [] sums = new int[arr.length];
	//现在采用的是哈希表,其实可以用数组
	HashMap<Integer,Integer> ends = new HashMap<Integer,Integer>();
	sums[arr.length -1] = arr[arr.length -1];
	ends.put(arr.length-1,arr.length-1);
	//从右向左算,如果右方值大于0,则此时无利可图,所以保存当前值和当前的位置
	//如果此时右方的值小于0,则此时将当前位置和右方的最小和求和便是当前位置上的最小和,返回当前和和之前的最小和到的最右位置
	for(int i = arr.length-2;i>=0;i++){
		if(sums[i+1]<0){
			sums[i] = arr[i]+sums[i+1];
			ends.put(i,ends.get(i+1));
		}else{
			sums[i] = arr[i];
			ends.put(i,i);
		}
	}
	int end = 0;
	int sum = 0;
	int res = 0;
	for(int i = 0;i <arr.length;i++){
		//当前位置求和跟k进行比较,如果此时求和的值小于等于k则一直累加
		while(end<arr.length && sum+sums[end] <= k){
			sum +=sums[end];
			end = ends.get(end)+1;
		}
		//如果此时再加下一个就超了,则开始执行下面的操作
		//判断end跟i的关系,如果此时i还没到则sum-0,如果当前位置等于end,则此时sum减去当前位置
		sum -=end >i ?arr[i] :0;
		res = Math.max(res,end -i);
		end = Math.max(end,i+1);
	}
	return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值