2102. 序列顺序查询
题目描述
一个观光景点由它的名字 name 和景点评分 score 组成,其中 name 是所有观光景点中 唯一 的字符串,score 是一个整数。景点按照最好到最坏排序。景点评分 越高 ,这个景点越好。如果有两个景点的评分一样,那么 字典序较小 的景点更好。
你需要搭建一个系统,查询景点的排名。初始时系统里没有任何景点。这个系统支持:
- 添加 景点,每次添加 一个 景点。
- 查询 已经添加景点中第
i好 的景点,其中i是系统目前位置查询的次数(包括当前这一次)。- 比方说,如果系统正在进行第
4次查询,那么需要返回所有已经添加景点中第4好的。
- 比方说,如果系统正在进行第
注意,测试数据保证 任意查询时刻 ,查询次数都 不超过 系统中景点的数目。
请你实现 SORTracker 类:
SORTracker()初始化系统。void add(string name, int score)向系统中添加一个名为name评分为score的景点。string get()查询第i好的景点,其中i是目前系统查询的次数(包括当前这次查询)。
示例:
输入:
["SORTracker", "add", "add", "get", "add", "get", "add", "get", "add", "get", "add", "get", "get"]
[[], ["bradford", 2], ["branford", 3], [], ["alps", 2], [], ["orland", 2], [], ["orlando", 3], [], ["alpine", 2], [], []]
输出:
[null, null, null, "branford", null, "alps", null, "bradford", null, "bradford", null, "bradford", "orland"]
解释:
SORTracker tracker = new SORTracker(); // 初始化系统
tracker.add("bradford", 2); // 添加 name="bradford" 且 score=2 的景点。
tracker.add("branford", 3); // 添加 name="branford" 且 score=3 的景点。
tracker.get(); // 从好到坏的景点为:branford ,bradford 。
// 注意到 branford 比 bradford 好,因为它的 评分更高 (3 > 2) 。
// 这是第 1 次调用 get() ,所以返回最好的景点:"branford" 。
tracker.add("alps", 2); // 添加 name="alps" 且 score=2 的景点。
tracker.get(); // 从好到坏的景点为:branford, alps, bradford 。
// 注意 alps 比 bradford 好,虽然它们评分相同,都为 2 。
// 这是因为 "alps" 字典序 比 "bradford" 小。
// 返回第 2 好的地点 "alps" ,因为当前为第 2 次调用 get() 。
tracker.add("orland", 2); // 添加 name="orland" 且 score=2 的景点。
tracker.get(); // 从好到坏的景点为:branford, alps, bradford, orland 。
// 返回 "bradford" ,因为当前为第 3 次调用 get() 。
tracker.add("orlando", 3); // 添加 name="orlando" 且 score=3 的景点。
tracker.get(); // 从好到坏的景点为:branford, orlando, alps, bradford, orland 。
// 返回 "bradford".
tracker.add("alpine", 2); // 添加 name="alpine" 且 score=2 的景点。
tracker.get(); // 从好到坏的景点为:branford, orlando, alpine, alps, bradford, orland 。
// 返回 "bradford" 。
tracker.get(); // 从好到坏的景点为:branford, orlando, alpine, alps, bradford, orland 。
// 返回 "orland" 。
提示:
name只包含小写英文字母,且每个景点名字互不相同。1 <= name.length <= 101 <= score <= 105- 任意时刻,调用
get的次数都不超过调用add的次数。 - 总共 调用
add和get不超过4 * 104
解法
方法一:有序集合
思考
系统需要在动态插入景点的同时,按「第 \(1\) 好、第 \(2\) 好、…」严格递增地查询。若每次查询都对全部景点排序,第 \(q\) 次查询的代价约为 \(O(n\log n)\),总操作次数较大时难以接受。
查询下标单调递增,且比较键为 \((-\textit{score},\textit{name})\),因此只需一个始终有序的序列,用下标直接取出当前名次。
为此我们用有序集合按该键维护全部景点,并用计数器 \(i\) 记录已查询次数,每次 \(\texttt{get}\) 将 \(i\) 加一并返回第 \(i\) 个元素的名字。
我们可以使用有序集合来存储景点,用一个变量 \(i\) 来记录当前查询的次数,初始时 \(i = -1\)。
调用 add 方法时,我们将景点的评分取负数,这样就可以使用有序集合按照评分从大到小排序,如果评分相同,按照景点名字的字典序从小到大排序。
调用 get 方法时,我们将 \(i\) 加一,然后返回有序集合中第 \(i\) 个景点的名字。
每次操作的时间复杂度为 \(O(\log n)\),其中 \(n\) 为已添加的景点数。空间复杂度为 \(O(n)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
方法二:双优先队列(大小根堆)
思考
方法一依赖有序集合的随机下标访问。若语言标准库没有平衡树,则需要另一套结构来配合「查询次数严格递增」这一限制。
与数据流中位数类似,可用两个堆把当前已公布的前若干名与尚未公布的较差景点分开:小根堆 \(\textit{good}\) 存已查询过的较好景点,大根堆 \(\textit{bad}\) 存其余景点。
\(\texttt{add}\) 时先将新景点挤入 \(\textit{good}\) 再把其中最差者放入 \(\textit{bad}\);\(\texttt{get}\) 则从 \(\textit{bad}\) 取出当前最好者并入 \(\textit{good}\),堆顶即为本次名次。比较时对名字取反序,使评分高、字典序小者更优先。
我们注意到,由于本题中的查询操作是按照严格递增的顺序进行的,因此我们可以使用类似于数据流中的中位数的方法,定义两个优先队列 good 和 bad,其中 good 是一个小根堆,存储当前最好的景点,bad 是一个大根堆,存储当前第 \(i\) 好的景点。
每次调用 add 方法时,我们将景点的评分和名字加入 good 中,然后将 good 中的最差的景点加入 bad 中。
每次调用 get 方法时,我们将 bad 中的最好的景点加入 good 中,然后返回 good 中的最差的景点。
每次操作的时间复杂度为 \(O(\log n)\),其中 \(n\) 为已添加的景点数。空间复杂度为 \(O(n)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |