https://leetcode-cn.com/problems/all-oone-data-structure/ 请你设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。
实现 AllOne 类:
- AllOne() 初始化数据结构的对象。
- inc(String key) 字符串 key 的计数增加 1 。如果数据结构中尚不存在 key ,那么插入计数为 1 的 key 。
- dec(String key) 字符串 key 的计数减少 1 。如果 key 的计数在减少后为 0 ,那么需要将这个 key 从数据结构中删除。测试用例保证:在减少计数前,key 存在于数据结构中。
- getMaxKey() 返回任意一个计数最大的字符串。如果没有元素存在,返回一个空字符串 “” 。
- getMinKey() 返回任意一个计数最小的字符串。如果没有元素存在,返回一个空字符串 “” 。
示例:
输入
["AllOne", "inc", "inc", "getMaxKey", "getMinKey", "inc", "getMaxKey", "getMinKey"]
[[], ["hello"], ["hello"], [], [], ["leet"], [], []]
输出
[null, null, null, "hello", "hello", null, "hello", "leet"]
解释
AllOne allOne = new AllOne();
allOne.inc("hello");
allOne.inc("hello");
allOne.getMaxKey(); // 返回 "hello"
allOne.getMinKey(); // 返回 "hello"
allOne.inc("leet");
allOne.getMaxKey(); // 返回 "hello"
allOne.getMinKey(); // 返回 "leet"
提示:
- 1 <= key.length <= 10
- key 由小写英文字母组成
- 测试用例保证:在每次调用 dec 时,数据结构中总存在 key
- 最多调用 inc、dec、getMaxKey 和 getMinKey 方法 5 * 104 次
思路:
- 数据结构1:保存每个key及其出现的次数。按序排列
- 数据结构2:优化查找key的速度,取消遍历数据结构1匹配key的过程。
基于以上思路, 数据结构1选取双向链表,因为随着inc操作,链表中的结点位置会频繁变动需要具备增删改操作都是O(1),且结构本身需要具备有序。 数据结构2选取哈希map,因为hash增删改查操作都是O(1)。
因此,我们可采用以下结构(错误示范,不满足时间复杂度O(1)):
list<pair<string, int> > lst;
unordered_map<string, list<pair<string, int> >::iterator > nodes;
按照上述结构,inc操作的伪代码如下:
inc操作:
如果key已经存在:计数+1
维护链表递增
如果key不存在:
添加新的key到lst与nodes中。
关键点在于如何维护链表递增:
循环执行:
当前计数 > 后一个key的计数: 交换位置
但是对于上述操作,有一种用例不满足题目要求。例如:当所有key在list中出现的次数相同时,此时再进行inc操作时,可能会有O(n)的时间复杂度。
示例:所有元素的计数值都为4,此时inc操作后,aaa的计数值为5 。 此时在维护链表有序时需要进行n次匹配。
因此,我们需要重新定义数据结构。
可以发现,当部分key的计数值一样时,我们需要维护链表有序时需要重复的进行比较与交换。而如果我们将重复的元素看做一个整体,那么维护链表有序就变得容易了。
示例:当我们inc:ttt时。只需要与后一个集合比较,如果相同则归为下一个集合中。
如果没有相同计数的集合,则新建一个集合(删除旧集合)
如果计数大于当前集合,则直接跳至下一集合(删除旧集合)
按照以上规则,我们只需要修改lst链表的结点即可。 数据结构定义:
list<pair<unordered_set<string>, int> > lst;
unordered_map<string, list<pair<unordered_set<string>, int> >::iterator > nodes;
结构示意:
代码参考官方题解;C++版
class AllOne {
list<pair<unordered_set<string>, int>> lst;
unordered_map<string, list<pair<unordered_set<string>, int>>::iterator> nodes;
public:
AllOne() {}
void inc(string key) {
if (nodes.count(key)) {
auto cur = nodes[key], nxt = next(cur);
if (nxt == lst.end() || nxt->second > cur->second + 1) {
unordered_set<string> s({key});
nodes[key] = lst.emplace(nxt, s, cur->second + 1);
} else {
nxt->first.emplace(key);
nodes[key] = nxt;
}
cur->first.erase(key);
if (cur->first.empty()) {
lst.erase(cur);
}
} else {
if (lst.empty() || lst.begin()->second > 1) {
unordered_set<string> s({key});
lst.emplace_front(s, 1);
} else {
lst.begin()->first.emplace(key);
}
nodes[key] = lst.begin();
}
}
void dec(string key) {
auto cur = nodes[key];
if (cur->second == 1) {
nodes.erase(key);
} else {
auto pre = prev(cur);
if (cur == lst.begin() || pre->second < cur->second - 1) {
unordered_set<string> s({key});
nodes[key] = lst.emplace(cur, s, cur->second - 1);
} else {
pre->first.emplace(key);
nodes[key] = pre;
}
}
cur->first.erase(key);
if (cur->first.empty()) {
lst.erase(cur);
}
}
string getMaxKey() {
return lst.empty() ? "" : *lst.rbegin()->first.begin();
}
string getMinKey() {
return lst.empty() ? "" : *lst.begin()->first.begin();
}
};
|