六月集训(22)有序集合

1.LeetCode:895. 最大频率栈

原题链接


        设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出出现频率最高的元素。

        实现 FreqStack 类:

        FreqStack() 构造一个空的堆栈。

        void push(int val) 将一个整数 val 压入栈顶。

        int pop() 删除并返回堆栈中出现频率最高的元素。

        如果出现频率最高的元素不只一个,则移除并返回最接近栈顶的元素。

        示例 1:

        输入:

        [“FreqStack”,“push”,“push”,“push”,“push”,“push”,“push”,“pop”,“pop”,“pop”,“pop”],

        [[],[5],[7],[5],[7],[4],[5],[],[],[],[]]

        输出:[null,null,null,null,null,null,null,5,7,5,4]

        提示:

        0 <= val <= 1e9

        push 和 pop 的操作数不大于 2 * 1e4。

        输入保证在调用 pop 之前堆栈中至少有一个元素。


        这里需要准备两个表,一张用来统计所有元素出现的频率 cnt,另一张用栈来统计某个频率的所有数字 stk。当我们要加入某个元素的时候,对于cnt表直接将value加一即可,对于stk表,对其频率的栈加入该元素即可,并更新最大频率。

        对于pop操作也很简单,先在出现频率最大的栈中删除该元素,随后其频率减减(该数字其他出现频率的栈中无需操作,因为他们也是按照先后顺序加入的),如果删除之后出现频率最大的栈中为空,最大直减一。

class FreqStack {
    unordered_map<int,int> s;
    int max=-100000000;
    map<int,stack<int>> stk;
public:
    FreqStack() {

    }
    
    void push(int val) {
        s[val]++;
        if(s[val]>max){
            max=s[val];
        }
        stk[s[val]].push(val);
    }
    
    int pop() {
        int x=stk[max].top();
        stk[max].pop();
        s[x]--;
        if(!stk[max].size()){
            max--;
        }
        return x;
    }
};

2.LeetCode:732. 我的日程安排表 III

原题链接


        当 k 个日程安排有一些时间上的交叉时(例如 k 个日程安排都在同一时间内),就会产生 k 次预订。

        给你一些日程安排 [start, end) ,请你在每个日程安排添加后,返回一个整数 k ,表示所有先前日程安排会产生的最大 k 次预订。

        实现一个 MyCalendarThree 类来存放你的日程安排,你可以一直添加新的日程安排。

        MyCalendarThree() 初始化对象。

        int book(int start, int end) 返回一个整数 k ,表示日历中存在的 k 次预订的最大值。

        示例:

        输入:

        [“MyCalendarThree”, “book”, “book”, “book”, “book”, “book”, “book”]

        [[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]

        输出:

        [null, 1, 1, 2, 3, 3, 3]

        提示:

        0 <= start < end <= 1e9

        每个测试用例,调用 book 函数最多不超过 400次


        这道题就很简单了,定义一张表,key为区间左端点,val为区间右端点,加入某个区间[l,r)就相当于在这个范围内都对数组元素进行了+1操作,这里我们利用差分思想最后再求前缀和并维护最大次数即可。

class MyCalendarThree {
    map<int,int> cnt;

public:
    MyCalendarThree() {
        cnt.clear();
    }
    
    int book(int start, int end) {
        cnt[start]++;
        cnt[end]--;
        int sum=0,maxv=0;
        for(auto i:cnt){
            sum+=i.second;
            maxv=max(sum,maxv);
        }
        return maxv;
    }
};

3.LeetCode:352. 将数据流变为多个不相交区间

原题链接


        给你一个由非负整数 a1, a2, …, an 组成的数据流输入,请你将到目前为止看到的数字总结为不相交的区间列表。

        实现 SummaryRanges 类:

        SummaryRanges() 使用一个空数据流初始化对象。

        void addNum(int val) 向数据流中加入整数 val 。

        int[][] getIntervals() 以不相交区间 [starti, endi] 的列表形式返回对数据流中整数的总结。

        示例:

        输入:

        [“SummaryRanges”, “addNum”, “getIntervals”, “addNum”, “getIntervals”, “addNum”, “getIntervals”, “addNum”, “getIntervals”, “addNum”, “getIntervals”]

        [[], [1], [], [3], [], [7], [], [2], [], [6], []]

        输出:

        [null, null, [[1, 1]], null, [[1, 1], [3, 3]], null, [[1, 1], [3, 3], [7, 7]], null, [[1, 3], [7, 7]], null, [[1, 3], [6, 7]]]

        提示:

        0 <= val <= 1e4

        最多调用 addNum 和 getIntervals 方法 3 * 1e4 次


        这道题还是相当有难度的,因为这里涉及到了区间的合并、重叠问题,(不过本题可以取巧,利用链表存储区间,再在遍历链表的过程中进行区间合并即可),首先我们定义一个set,用来存储区间,set中存储的是某个区间的映射 :l * base+r,要得到该区间很简单,该元素除以base为左端点,模上base为右端点(需要满足base一直大于右端点,观察数据范围设置一个合适的值即可),这样做是无需我们重载小于号了,在set中会自动按该映射的大小来从小到大进行排序,这里的操作和我们常用的将二维数组坐标转化为一维类似。

        对于得到区间没什么好说的,遍历set加入区间即可。加入区间才是本题的难点,首先我们得到该val的映射key=val*base+vak,然后去set中利用lower_bound找到映射大于等于他的位置。如果找不到直接在set中插入key并返回即可,而找到的情况就需要我们进行思考了。

        这里我们已经得到了第一个大于等于要插入点的区间。我们考虑合并的情况:

        (1):由于该映射比key要大,所以其左右端点l ,r 起码有一个是大于我们的val的。如果val+1>=l的话就说明该点可以加入到该区间中并且val是新的左端点,r是新的右端点(举个例子:val的区间为[2,2],找到的区间可能为 [2,2],[2,3],[3,5] ),这个时候先删除找到的区间然后更新左右区间插入到set中即可,这是区间为[val,r]。

        (2):如果val+l>=l没有满足,就说明set中没有比val的区间大的,我们直接插入[val,val]即可。

        这里书写代码的时候还是有些细节的,在第一种情况下我们先删除区间[l,r],第二种情况下我们让r=val,判断完两种情况后都让l=val,并插入[l,r](当然也可以不这么写,这样只是提高了一些复用性)。

        比key大的区间我们考虑完毕了,现在考虑比key小的,这里就利用之前我们找到的迭代器让他自减即可(不为begin的情况下),其比key小也说明了两种情况,我们将其的左右端点称为x,y:

        (1)x<=val&&y>=val:由于我们映射得到的key,左端点是站主导因素的所以这种情况是可能发生的。比如key的区间为[2,2],找到的区间为[1,3],这种情况下,我们需要删除之前插入的[l,r],因为新的区间就是[x,y]。

        (2)当不满足上述条件的时候我们考虑y与l的关系,如果y+1>=l,二者就可以合并为新区间[x,r](比如[x,y]=[1,5],[l,r]=[4,8]或者[6,8]或者[5,8]),这个时候删除二者原区间在插入[x,r]即可。

        那么这样我们就可以看成本题区间的合并和插入分为五种情况

        (1):区间正好位于两个区间之间。(比如key=[3,5], 这两个区间为[1,1], [7,8])

        (2):区间插入了某个区间内部。(比如key=[3,5],该区间为[2,6]或者[3,6]或者[2,5]或者[1,6])

        (3):区间的右端点和某个区间的左端点可以合并。(比如key=[3,3],该区间为[3,8])

        (4):区间的左端点和某个区间的右端点可以合并。(比如key=[4,5],该区间为[1,3]或者[2,4])

        (5):区间的插入可以使某两个区间合并(例如key=[3,5],这两个区间为[2,4],[6,8]),只不过按照我们的思路是可以解决这种情况的所以没有单独列出来。

class SummaryRanges {
    #define base 10001
    set<int> s;
public:
    SummaryRanges() {
        s.clear();
    }
    
    void addNum(int val) {
        int key=val*base+val;
        if(s.size()==0){
            s.insert(key);
            return;
        }
        int l=val,r=val;
        auto it=s.lower_bound(key);
        if(it!=s.end()){
            l=(*it)/base;
            r=(*it)%base;
            if(val+1>=l){
                s.erase(l*base+r);
            }else {
                r=val;
            }
            l=val;
        }
        s.insert(l*base+r);
        it=s.find(l*base+r);
        if(it!=s.begin()){
            it--;
            int x=(*it)/base;
            int y=(*it)%base;
            if(x<=l&&y>=r){
                s.erase(l*base+r);
            }else if(y+1>=l){
                s.erase(x*base+y);
                s.erase(l*base+r);
                s.insert(x*base+r);
            }
        }
    }
    
    vector<vector<int>> getIntervals() {
        vector<vector<int>> ans;
        for(auto i:s){
            ans.push_back({i/base,i%base});
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值