Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get
and put
.html
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.put(key, value)
- Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.算法
Follow up:
Could you do both operations in O(1) time complexity?post
Example:this
LFUCache cache = new LFUCache( 2 /* capacity */ ); cache.put(1, 1); cache.put(2, 2); cache.get(1); // returns 1 cache.put(3, 3); // evicts key 2 cache.get(2); // returns -1 (not found) cache.get(3); // returns 3. cache.put(4, 4); // evicts key 1. cache.get(1); // returns -1 (not found) cache.get(3); // returns 3 cache.get(4); // returns 4
这道题是让咱们实现最近不经常使用页面置换算法LFU (Least Frequently Used), 以前咱们作过一道相似的题LRU Cache,让咱们求最近最少使用页面置换算法LRU (Least Recnetly Used)。两种算法虽然名字看起来很类似,可是实际上是不一样的。顾名思义,LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰必定时间内被访问次数最少的页面。光说无凭,举个例子来看看,好比说咱们的cache的大小为3,而后咱们按顺序存入 5,4,5,4,5,7,这时候cache恰好被装满了,由于put进去以前存在的数不会占用额外地方。那么此时咱们想再put进去一个8,若是使用LRU算法,应该将4删除,由于4最久未被使用,而若是使用LFU算法,则应该删除7,由于7被使用的次数最少,只使用了一次。相信这个简单的例子能够大概说明两者的区别。url
这道题比以前那道LRU的题目还要麻烦一些,由于那道题只要用个list把数字按时间顺序存入,链表底部的位置老是最久未被使用的,每次删除底部的值便可。而这道题不同,因为须要删除最少次数的数字,那么咱们必需要统计每个key出现的次数,因此咱们用一个哈希表m来记录当前数据{key, value}和其出现次数之间的映射,这样还不够,为了方便操做,咱们须要把相同频率的key都放到一个list中,那么须要另外一个哈希表freq来创建频率和一个里面全部key都是当前频率的list之间的映射。因为题目中要咱们在O(1)的时间内完成操做了,为了快速的定位freq中key的位置,咱们再用一个哈希表iter来创建key和freq中key的位置之间的映射。最后固然咱们还须要两个变量cap和minFreq,分别来保存cache的大小,和当前最小的频率。spa
为了更好的讲解思路,咱们仍是用例子来讲明吧,咱们假设cache的大小为2,假设咱们已经按顺序put进去5,4,那么来看一下内部的数据是怎么保存的,因为value的值并非很重要,为了避免影响key和frequence,咱们采用value#来标记:code
m:htm
5 -> {value5, 1}blog
4 -> {value4, 1}ip
freq:
1 -> {5,4}
iter:
4 -> list.begin() + 1
5 -> list.begin()
这应该不是很难理解,m中5对应的频率为1,4对应的频率为1,而后freq中频率为1的有4和5。iter中是key所在freq中对应链表中的位置的iterator。而后咱们的下一步操做是get(5),下面是get须要作的步骤:
1. 若是m中不存在5,那么返回-1
2. 从freq中频率为1的list中将5删除
3. 将m中5对应的frequence值自增1
4. 将5保存到freq中频率为2的list的末尾
5. 在iter中保存5在freq中频率为2的list中的位置
6. 若是freq中频率为minFreq的list为空,minFreq自增1
7. 返回m中5对应的value值
通过这些步骤后,咱们再来看下此时内部数据的值:
m:
5 -> {value5, 2}
4 -> {value4, 1}
freq:
1 -> {4}
2 -> {5}
iter:
4 -> list.begin()
5 -> list.begin()
这应该不是很难理解,m中5对应的频率为2,4对应的频率为1,而后freq中频率为1的只有4,频率为2的只有5。iter中是key所在freq中对应链表中的位置的iterator。而后咱们下一步操做是要put进去一个7,下面是put须要作的步骤:
1. 若是调用get(7)返回的结果不是-1,那么在将m中7对应的value更新为当前value,并返回
2. 若是此时m的大小大于了cap,即超过了cache的容量,则:
a)在m中移除minFreq对应的list的首元素的纪录,即移除4 -> {value4, 1}
b)在iter中清除4对应的纪录,即移除4 -> list.begin()
c)在freq中移除minFreq对应的list的首元素,即移除4
3. 在m中创建7的映射,即 7 -> {value7, 1}
4. 在freq中频率为1的list末尾加上7
5. 在iter中保存7在freq中频率为1的list中的位置
6. minFreq重置为1
通过这些步骤后,咱们再来看下此时内部数据的值:
m:
5 -> {value5, 2}
7 -> {value7, 1}
freq:
1 -> {7}
2 -> {5}
iter:
7 -> list.begin()
5 -> list.begin()
参见代码以下:
class LFUCache { public: LFUCache(int capacity) { cap = capacity; } int get(int key) { if (m.count(key) == 0) return -1; freq[m[key].second].erase(iter[key]); ++m[key].second; freq[m[key].second].push_back(key); iter[key] = --freq[m[key].second].end(); if (freq[minFreq].size() == 0) ++minFreq; return m[key].first; } void put(int key, int value) { if (cap <= 0) return; if (get(key) != -1) { m[key].first = value; return; } if (m.size() >= cap) { m.erase(freq[minFreq].front()); iter.erase(freq[minFreq].front()); freq[minFreq].pop_front(); } m[key] = {value, 1}; freq[1].push_back(key); iter[key] = --freq[1].end(); minFreq = 1; } private: int cap, minFreq; unordered_map<int, pair<int, int>> m; unordered_map<int, list<int>> freq; unordered_map<int, list<int>::iterator> iter; };
相似题目:
参考资料:
https://leetcode.com/problems/lfu-cache/
https://discuss.leetcode.com/topic/69436/concise-c-o-1-solution-using-3-hash-maps-with-explanation