从0开始的秋招刷题路,记录下所刷每道题的题解,帮助自己回顾总结
715. Range 模块
Range模块是跟踪数字范围的模块。设计一个数据结构来跟踪表示为 半开区间 的范围并查询它们。
半开区间 [left, right) 表示所有 left <= x < right 的实数 x 。
实现 RangeModule 类:
RangeModule() 初始化数据结构的对象。
void addRange(int left, int right) 添加 半开区间 [left, right),跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间 [left, right) 中尚未跟踪的任何数字到该区间中。
boolean queryRange(int left, int right) 只有在当前正在跟踪区间 [left, right) 中的每一个实数时,才返回 true ,否则返回 false 。
void removeRange(int left, int right) 停止跟踪 半开区间 [left, right) 中当前正在跟踪的每个实数。
示例 1:
输入
[“RangeModule”, “addRange”, “removeRange”, “queryRange”, “queryRange”, “queryRange”]
[[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 17]]
输出
[null, null, null, true, false, true]
解释
RangeModule rangeModule = new RangeModule();
rangeModule.addRange(10, 20);
rangeModule.removeRange(14, 16);
rangeModule.queryRange(10, 14); 返回 true (区间 [10, 14) 中的每个数都正在被跟踪)
rangeModule.queryRange(13, 15); 返回 false(未跟踪区间 [13, 15) 中像 14, 14.03, 14.17 这样的数字)
rangeModule.queryRange(16, 17); 返回 true (尽管执行了删除操作,区间 [16, 17) 中的数字 16 仍然会被跟踪)
提示:
1 <= left < right <=
1
0
9
10^9
109
在单个测试用例中,对 addRange 、 queryRange 和 removeRange 的调用总数不超过
1
0
4
10^4
104 次
方法一:TreeMap模拟: 1、建立一个有序映射表,key是区间的左端点,对应的值是区间的右端点; 2、添加区间的时候,要把这个区间跟所有有交集的区间(包括端点相邻的区间)都合并,有重复的要删除,再添加进去合并后的总区间; 3、删除的时候,要把所有完全包裹的区间都删除,没有完全包裹的,修改为去掉交集的区间; 4、判断的时候很简单,找到那个距离所查找区间内最近的那区间看是否完全包裹就好了
class RangeModule {
TreeMap<Integer,Integer> map;
public RangeModule() {
map=new TreeMap<>();
map.put(-1,-1);
map.put((int)1e9+5,(int)1e9+5);
}
public void addRange(int left, int right) {
List<int[]> list=new ArrayList<>();
while(true){
int a=map.floorKey(left);
if(map.get(a)>=left){
list.add(new int[]{a,map.get(a)});
map.remove(a);
}
else{break;}
}
while(true){
int a=map.ceilingKey(left);
if(a<=right){
list.add(new int[]{a,map.get(a)});
map.remove(a);
}
else{break;}
}
if(list.size()==0){map.put(left,right);}
else{map.put(Math.min(list.get(0)[0],left),Math.max(right,list.get(list.size()-1)[1]));}
}
public boolean queryRange(int left, int right) {
return map.get(map.floorKey(left))>=right;
}
public void removeRange(int left, int right) {
List<int[]> list=new ArrayList<>();
while(true){
int a=map.floorKey(left);
if(map.get(a)>left){
list.add(new int[]{a,map.get(a)});
map.remove(a);
}
else{break;}
}
while(true){
int a=map.ceilingKey(left);
if(a<=right){
list.add(new int[]{a,map.get(a)});
map.remove(a);
}
else{break;}
}
if(list.size()>0){
if(list.get(0)[0]<left){map.put(list.get(0)[0],left);}
if(list.get(list.size()-1)[1]>right){map.put(right,list.get(list.size()-1)[1]);}
}
}
}
方法二:线段树。: 1、首先初始化线段树,总树最大区间[0,1e9]; 2、添加区间操作:需要递归找到存在的最小的可以覆盖要添加的区间的那个子树,假如这个子树的covered参数是true,那么无需进一步操作,,否则需要向下继续找到那些最大的,存在的,并且可以被添加区间包裹住的子树(或者把这些最大的区间new出来),并且把这些区间的covered参数设为true,同时删除所有它们的子区间; 3、查询操作:递归查找所有最大的,可以被所查询区间包裹住的所有最靠上的那些子树,这些子树必须全是covered==true,且不能是空; 4、区间删除,这个操作很麻烦:首先假如一处区间对应的那个树是空,那么说明这些数字根本不存在,无需操作,,(1)假如移除到了某个子树的时候covered为true:首先如果移除的区间内可以精准覆盖这个子树,那么直接把covered设为false后删除所有它的子树即可,,其次如果无法精准覆盖,那么需要恢复这个子树的左右子树并设covered为true,再分别进行处理,,,(2)假如说碰到了某个子树的covered参数为false,这时只能说明这里的数字不全存在,而不是全不存在,那么需要递归继续操作左右子树(千万不要就此终止呀),最后不管如何要把covered设为false
class SegTree{
int l,r;//表示的是这个闭区间代表的左右边界
SegTree left,right;
boolean covered;//区间内的数字是否都被覆盖了
public SegTree(int l,int r){
this.l=l;
this.r=r;
this.covered=false;
}
public SegTree(int l,int r,boolean covered){
this.l=l;
this.r=r;
this.covered=covered;
}
}
class RangeModule {
SegTree st;
public RangeModule() {
st=new SegTree(0,(int)1e9);
}
public void addRange(int left, int right) {
addNum(st,left,right-1);
}
void addNum(SegTree t,int a,int b){
//此方法在线段树中添加闭区间[a,b]
int l=t.l,r=t.r,mid=(l+r)>>1;
if(t.covered){return;}//区间内数字已经全部存在,无需进一步操作
if(l>=a&&r<=b){
//完全精准覆盖(所有添加操作最后一步都会到这里),此时只需要把这个区间设为true,删除所有子区间即可
t.covered=true;
t.left=t.right=null;
}
else if(b<=mid){
//只更新左半边即可
if(t.left==null){t.left=new SegTree(l,mid);}
addNum(t.left,a,b);
}
else if(a>mid){
//只更新右半边即可
if(t.right==null){t.right=new SegTree(mid+1,r);}
addNum(t.right,a,b);
}
else{
//同时更新左右半边
if(t.left==null){t.left=new SegTree(l,mid);}
if(t.right==null){t.right=new SegTree(mid+1,r);}
addNum(t.left,a,mid);
addNum(t.right,mid+1,b);
}
}
public boolean queryRange(int left, int right) {
return allCovered(st,left,right-1);
}
boolean allCovered(SegTree t,int a,int b){
//此方法判断在闭区间[a,b]的所有数字是否都存在
if(t==null){return false;}
int l=t.l,r=t.r,mid=(l+r)>>1;
if(t.covered){return true;}//所有的判断操作最后叶处都会分为这样的小操作
if(b<=mid){return allCovered(t.left,a,b);}//在左边判断
else if(a>mid){return allCovered(t.right,a,b);}//在右边判断
return allCovered(t.left,a,mid)&&allCovered(t.right,mid+1,b);//在两边判断
}
public void removeRange(int left, int right) {
removeNum(st,left,right-1);
}
void removeNum(SegTree t,int a,int b){
//此方法移除树内闭区间[a,b]的所有数字
if(t==null){return;}//区间不存在,说明本来就不存在这些数字
int l=t.l,r=t.r,mid=(l+r)>>1;
if(l>b||r<a){return;}
if(l>=a&&r<=b){
//如果删除的区间完全包括树的区间,那么设为false后,删除所有子区间
t.left=t.right=null;
}
else if(t.covered){
//此时线段树区间大于删除区间且区间内数字都存在,那么需要恢复左右子树后进行操作
t.left=new SegTree(l,mid,true);
t.right=new SegTree(mid+1,r,true);
if(b<=mid){
//需要处理左半部分,但是需要保持右半部分的情况与t相同,剩余的部分要纹丝不动
removeNum(t.left,a,b);
}
else if(a>mid){
//需要处理右半部分,但是需要保持左半部分的情况与t相同,剩余的部分要纹丝不动
removeNum(t.right,a,b);
}
else{
//需要处理左右两部分
removeNum(t.left,a,mid);
removeNum(t.right,mid+1,b);
}
}
else{
//此时树内的数字不全存在,需要进一步看左右子树的情况后再进行操作
if(b<=mid){removeNum(t.left,a,b);}
else if(a>mid){removeNum(t.right,a,b);}
else{
removeNum(t.left,a,mid);
removeNum(t.right,mid+1,b);
}
}
t.covered=false;//这个区间内的数字已经不全存在,因此要设成false
}
}