LC 2671. 频率跟踪器,一道中规中矩的数据结构题目,解法平平无奇,却让我学到了「阅读理解」的重要性。
2671. 频率跟踪器#
https://leetcode.cn/problems/frequency-tracker/
实现 FrequencyTracker 类:
- FrequencyTracker():使用一个空数组初始化 FrequencyTracker 对象。
- void add(int number):添加一个 number 到数据结构中。
- void deleteOne(int number):从数据结构中删除一个 number 。数据结构 可能不包含 number ,在这种情况下不删除任何内容。
- bool hasFrequency(int frequency): 如果数据结构中存在出现 frequency 次的数字,则返回 true,否则返回 false。
Map+Set版本#
- 维护每个数据的频率
num2Freq
;
- 同时维护频率对应数据集合
freq2Nums
;
- 数据集合使用
有序结构
or set
,保证读写效率;
思路平平无奇。
实现提交,发现1118个用例,卡在了第1024个上。
百思不得其解,debug
两遍也没想出原因。然后在地铁上看了下其他人的解法,发现了卡点:原来是我「阅读理解」出了问题。
我理解错了 deleteOne 的含义,我开始理解的「从数据结构中删除一个 number」是将数据+频率全部删除。
而实际上数据是要降低一次频率!
搞明白错在哪里,代码修正就很快了,如下是修正后的代码。
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
40
|
static class SetSolution {
Map<Integer, Integer> num2Freq;
Map<Integer, Set<Integer>> freq2Nums;
public SetSolution() {
num2Freq = new HashMap<>();
freq2Nums = new HashMap<>();
}
public void add(int number) {
int freq = num2Freq.getOrDefault(number, 0) + 1;
num2Freq.put(number, freq);
freq2Nums.computeIfAbsent(freq, k -> new HashSet<>());
freq2Nums.get(freq).add(number);
if (freq2Nums.containsKey(freq - 1)) {
// 移除较低频率中的当前数据
freq2Nums.get(freq - 1).remove(number);
}
}
public void deleteOne(int number) {
if (!num2Freq.containsKey(number)) {
return;
}
int freq = num2Freq.get(number);
num2Freq.remove(number);
freq2Nums.get(freq).remove(number);
if (freq - 1 > 0) {
// 频率-1的情况
num2Freq.put(number, freq - 1);
freq2Nums.computeIfAbsent(freq - 1, k -> new HashSet<>());
freq2Nums.get(freq - 1).add(number);
}
}
public boolean hasFrequency(int frequency) {
return freq2Nums.containsKey(frequency) && !freq2Nums.get(frequency).isEmpty();
}
}
|
复杂度分析#
增、改、查时间复杂度均为O(1)
。
使用额外的Map
+Set
,空间复杂度O(n)
。
优化思路,只记录频率下数据量#
观察上面解法,同时再读题,会发现数据操作仅存在 add
/delete
,每一次频率内数据要么多一个少一个,不存在其他信息获取需求,解法1中freq2Nums
维护了频率丢应所有原数据,实际上我们仅需要获取频率对应数据量(即频率的数据频率),因此仅维护频率对应数据量即可。
优化后,空间可节省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
29
30
31
32
33
34
35
36
37
38
39
40
41
|
static class CntSolution {
// 数据元素对应频率
Map<Integer, Integer> num2Freq;
// 频率对应数据量;由于数据操作仅存在 add/delete,每一次频率内数据要么多一个少一个,不存在其他信息获取需求,因此仅维护频率对应数据量即可
Map<Integer, Integer> freq2Cnt;
public CntSolution() {
num2Freq = new HashMap<>();
freq2Cnt = new HashMap<>();
}
public void add(int number) {
int oldFreq = num2Freq.getOrDefault(number, 0);
int newFreq = oldFreq + 1;
num2Freq.put(number, newFreq);
if (oldFreq > 0) {
freq2Cnt.put(oldFreq, freq2Cnt.get(oldFreq) - 1);
}
freq2Cnt.put(newFreq, freq2Cnt.getOrDefault(newFreq, 0) + 1);
}
public void deleteOne(int number) {
if (!num2Freq.containsKey(number)) {
return;
}
int oldFreq = num2Freq.get(number);
int newFreq = oldFreq - 1;
if (newFreq == 0) {
num2Freq.remove(number);
} else {
num2Freq.put(number, newFreq);
freq2Cnt.put(newFreq, freq2Cnt.getOrDefault(newFreq, 0) + 1);
}
freq2Cnt.put(oldFreq, freq2Cnt.get(oldFreq) - 1);
}
public boolean hasFrequency(int frequency) {
return freq2Cnt.containsKey(frequency) && freq2Cnt.get(frequency) > 0;
}
}
|
复杂度分析#
同解法1,实际执行的空间效率更高。
本题中规中矩,很容易产出思路。
但也正像平时与上下游同事沟通,很容易出现理解上的偏差。因此要重视「阅读理解」。