懒删除堆
如何「优雅」地删除元素在计算机领域中是一个十分重要的问题:比如删除磁盘中的文件,是每删一条记录就写一次磁盘、还是聚集多条待删除的记录再写磁盘?删除是物理意义上的删除,即将那片空间全部的比特全部设为 0、还是仅仅标记删除?
堆(优先级队列、二叉堆)中删除元素的时间复杂度是 O(n) 级别的,必须遍历一遍元素才能找到待删除的元素;事实上,很多语言的优先级队列的 API 中是没有 remove() 方法的。Java 中的优先级队列虽然支持 remove() 方法,但是不应该频繁使用。本文介绍一种在堆中「懒」删除元素的方法。
例题引入
参考 2353. 设计食物评分系统 - 力扣(Leetcode)
思路分析
这道题中涉及到对堆中的元素进行更新。常规的想法是,我们将旧的值 remove 出去,再 add 新的值。但是 remove 的时间复杂度是线性的,在堆中的效率是很低的。事实上,我们完全可以跳过删除元素这一步,直接把新的值加入堆中;
当需要对堆进行 peek 或是 poll 操作时,必须首先检查堆顶的元素此时是否是「过期」的。我们可以用一个容器来保存某元素当前最新的值,例如哈希表
1 |
|
以下是几点说明
- JDK 原生没有 Pair 类,如果想使用 Pair 类,使用 JavaFX 中的 Pair 类
- 如果确实需要堆中保存的都是最新的元素,可以考虑使用有序集合,在 Java 中对应 TreeSet。有序集合增加、删除元素的时间复杂度是 O(logn),查找集合中最小元素的复杂度也是 O(logn);作为对比,二叉堆增加元素的时间复杂度是 O(logn),删除元素是 O(n),查找集合中最小元素的复杂度是 O(1)。如果仅关心集合中的最小元素,使用懒删除堆更合适一些
其他例题
-
100258. 最高频率的 ID - 力扣(LeetCode)
周赛时这题做得很不好…
这道题也是懒删除堆技巧的典型应用了。我们直接将新构造的 MyClass 实例压入堆中,在需要判断当前出现频率最高的 ID 时,检查当前堆顶元素的 cnt 值是否与哈希表中的新值相等
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
32
33
34
35
36
37
38
39
40class Solution {
public long[] mostFrequentIDs(int[] nums, int[] freqs) {
int n = nums.length;
long[] ans = new long[n];
HashMap<Integer, Long> map = new HashMap<>();
PriorityQueue<MyClass> maxHeap = new PriorityQueue<>(new Comparator<MyClass>() {
@Override
public int compare(MyClass o1, MyClass o2) {
if (o2.cnt - o1.cnt >= 0) {
return 1;
}
return -1;
}
});
for (int i = 0; i < n; i++) {
int num = nums[i];
int freq = freqs[i];
long oldCnt = map.getOrDefault(num, 0L);
long curCnt = oldCnt + freq;
map.put(num, curCnt);
maxHeap.add(new MyClass(num, curCnt));
while (map.get(maxHeap.peek().id) != maxHeap.peek().cnt) {
// delete outdated info
maxHeap.poll();
}
ans[i] = maxHeap.peek().cnt;
}
return ans;
}
class MyClass {
int id;
long cnt;
MyClass(int id, long cnt) {
this.id = id;
this.cnt = cnt;
}
}
}这道题的另一个解法是维护一个 key 为频率,value 为频率的频率的有序哈希表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public long[] mostFrequentIDs(int[] nums, int[] freqs) {
int n = nums.length;
long[] ans = new long[n];
HashMap<Integer, Long> map = new HashMap<>();
TreeMap<Long, Long> treeMap = new TreeMap<>(Collections.reverseOrder()); // in descending order
for (int i = 0; i < n; i++) {
int num = nums[i];
int freq = freqs[i];
long oldCnt = map.getOrDefault(num, 0L);
long curCnt = oldCnt + freq;
treeMap.put(curCnt, treeMap.getOrDefault(curCnt, 0L) + 1L);
treeMap.put(oldCnt, treeMap.getOrDefault(oldCnt, 1L) - 1L);
map.put(num, curCnt);
while (treeMap.firstEntry().getValue() == 0) {
treeMap.pollFirstEntry();
}
if (treeMap.isEmpty()) {
ans[i] = 0;
} else {
ans[i] = treeMap.firstKey();
}
}
return ans;
}
懒删除堆
https://exapricity.tech/Lazy-Heap.html