Leetcode力扣秋招刷题路-0715

文章介绍了如何设计一个RangeModule类,用于跟踪半开区间并支持添加、删除和查询操作。提供了两种解决方案:使用TreeMap进行区间模拟和利用线段树实现高效查询。这两种方法都涉及到区间合并、查询和删除的逻辑处理。
摘要由CSDN通过智能技术生成

从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
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fffffffyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值