LeetCode2102. 序列顺序查询

170 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

题目描述: 一个观光景点由它的名字 name 和景点评分 score 组成,其中 name 是所有观光景点中 唯一 的字符串,score 是一个整数。景点按照最好到最坏排序。景点评分 越高 ,这个景点越好。如果有两个景点的评分一样,那么 字典序较小 的景点更好。

你需要搭建一个系统,查询景点的排名。初始时系统里没有任何景点。这个系统支持:

  • 添加 景点,每次添加 一个 景点。
  • 查询 已经添加景点中第 i 好 的景点,其中 i 是系统目前位置查询的次数(包括当前这一次)。 比方说,如果系统正在进行第 4 次查询,那么需要返回所有已经添加景点中第 4 好的。 注意,测试数据保证 任意查询时刻 ,查询次数都 不超过 系统中景点的数目。

请你实现 SORTracker 类:

  • SORTracker() 初始化系统。
  • void add(string name, int score) 向系统中添加一个名为name 评分为 score 的景点。
  • string get() 查询第 i 好的景点,其中 i 是目前系统查询的次数(包括当前这次查询)。

分析:本题是数据结构设计类的题,有两个需要实现的函数add()get(),其中比较关键的是get()函数。get需要返回第i好的结点,其中i是调用get的次数。

本题给出的数据范围为:

  • 1 <= name.length <= 10
  • 1 <= score <= 10^5
  • 任意时刻,调用 get 的次数都不超过调用 add 的次数。
  • 总共 调用 add 和 get 不超过 4 * 104 

从数据范围可以看出,如果单次调用get()的复杂度超过O(n)O(n),就有可能会超时,get()比较理想的时间复杂度为O(1)O(1)或者O(logn)O(\log n),这样总的时间复杂度为O(nlogn)O(n\log n),在10510^5%的范围内不会超时。 注:有关于从力扣题目给出的数据范围反推正确算法的时间复杂度的分析网上有很多了,可以自行搜索。

方法一:两个优先队列

本题和LeetCode295. 数据流的中位数思路类似,在295中,我们使用了两个堆来保存数据,保持两个堆的数量差值不超过2,这样中位数就永远在堆顶,可以在O(1)O(1)时间内取得中位数。 在本题中,我们需要找到的是第ii大的数,仿照295中的思路

使用两个优先队列llrrll保存前i1i - 1个记录,ii为下次查询的编号。 ll的堆顶是小根堆,rr是大根堆。

  • add: 添加新的记录,那么需要将该记录添加到l,再把l堆顶的记录弹出,放到r中。
  • get: 将r堆顶的记录弹出,放到l中,返回l堆顶记录。 注意本题高的元素是score大但是name的字典序小,两者的方向相反,可以做一个特殊处理。存放-scorename,那么l就应当是大根堆,r是小根堆
typedef pair<int, string> PIS;
class SORTracker {
public:
    SORTracker() {

    }
    
    void add(string name, int score) {
        l.push({-score, name});
        r.push(l.top());
        l.pop();
    }
    
    string get() {
        l.push(r.top());
        r.pop();
        return l.top().second;
    }
private:
    priority_queue<PIS, vector<PIS>, less<PIS>> l;
    priority_queue<PIS, vector<PIS>, greater<PIS>> r;
};

时间复杂度分析:堆的单次插入操作为O(logn)O(\log n)get()单次为O(1)O(1), 总的时间复杂度为O(nlogn)O(n\log n),其中nn为插入的总数据量。

方法二:平衡树

利用编程语言自带的平衡树数据结构,可以使插入的数据一直保持有序,同时可以在很小的时间复杂度内找到前驱或者后继。 以c++为例,c++ stl中的set是平衡树的一种实现, 同时可以通过set的迭代器往前或者往后遍历。 初始时让迭代器it指向末尾, 每次调用add时,如果新插入的数据位于迭代器it之前,那么就需要移动迭代器,否则不变。 每次调用get时,返回迭代后的数据,然后后移迭代器it

typedef pair<int, string> PIS;
class SORTracker {
public:
    SORTracker() {
        it = s.end();
    }
    
    void add(string name, int score) {
        if (*s.emplace(-score, name).first < *it) --it;
    }
    
    string get() {
        return (it++)->second;
    }
private:
    set<pair<int, string>> s;
    set<pair<int, string>>::iterator it;
};

时间复杂度分析:set插入操作O(logn)O(\log n),总的时间复杂度为O(nlogn)O(n\log n),其中nn为插入的总数据量。