432. 全 O(1) 的数据结构
考点
- 哈希表与链表的综合
- 掌握迭代器、
pair
、unordered_set
与unordered_map
的本质
题解
1 | class AllOne { |
思路
本题难度极高,对哈希表、链表、迭代器的熟练度都是极大的考验,是非常棒的综合题!
在完成本题前,请先完成460. LFU 缓存
与146. LRU 缓存
,掌握基本的哈希表与链表的结合思路
题目希望给每个字符串计数,可增可减,所有操作为常数时间复杂度;并返回计数最大与最小的字符串,如果计数为0,则将其删除
可以参考LFU,只使用变量来记录极值吗?
LFU是单向操作,只增无减,统计极值用变量即可胜任
本题有增有减,只用变量很难记录空间的动态变化,必须依赖容器来完成这一操作
可以参考LFU的数据结构吗?
若参考LFU,使用
unordered_map
,则键为次数,给每个键单独新建链表作为值那么只能用变量来记录键的极值,因为
unordered_map
是无序的,方才提过用变量是死路若使用
map
,键虽有序但达不到常数时间复杂度故而LFU的数据结构无法照搬
联想LRU用到的有序双向链表技巧,表头是最晚访问元素,表尾是最早访问元素,极值就在两侧
可以设计数据结构如下:
新建一个双向链表,链表节点为键值对,键为次数,值为
unordered_set
以存储字符串次数增加,则在当前节点之后新增节点/修改下一个节点;次数减少,则在当前节点之前新增节点/修改前一个节点
从而保证了链表表头为次数最小值,链表表尾为次数最大值
新建一个哈希表,键为字符串,值为对应次数的链表节点地址

为什么设计“左小右大”?而不是像LRU一样”左大右小“?
若是
左大右小
,每次新增的节点都将在链表表尾,返回表尾元素的迭代器则是rbegin方法,且这个迭代器是reverse_iterator
类型reverse_iterator
类型是对iterator
类型的封装,可参见双向链表list删除节点时,使用的是erase方法,它只支持
iterator
类型参数返回表头元素迭代器的方法begin,满足这一条件。故而选择
左小右大
为什么双向链表的节点不选择
unordered_map
,而选择pair
?因为
unordered_map
与map
都没有取出键
这一个操作,哈希表的意义就是通过键访问值
实际上,我们需要的是一个类似结构体的容器,第一个元素存储次数,第二个元素存储
unordered_set
这样一来前后节点比较次数时,直接取节点容器的第一个元素对比即可
pair
恰好满足,当然你乐意的话可以自写结构体为什么存储字符串选择
unordered_set
,而不是vector
?因为字符串在不同节点之间转移时,所经历的步骤就是
- 从原节点存储删除
- 插入新节点存储
知道一个值,怎么在常数时间复杂度从存储中删除呢?只有哈希表了!
哈希表当中键与值相等的是什么STL?当然是
unordered_set
和set