【6】460. LFU Cache
460. LFU Cache
- Total Accepted: 5305
- Total Submissions: 26292
- Difficulty: Hard
- Contributors: 1337c0d3r , fishercoder
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get
and put
.
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?
Example:
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 cache)设计实现数据结构。应当支持get和set操作。
get(key) - 如果存在key,返回其对应的value,否则返回-1。
set(key, value) - 如果不存在key,新增value;否则替换原始value。当缓存容量满时,应当将最不常使用的项目移除。如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目。
进一步思考:
能否在O(1)时间完成操作?
解题思路:
双向链表(Doubly Linked List) + 哈希表(Hash Table)
首先定义双向链表节点:KeyNode(Key节点)与FreqNode(频度节点)。
KeyNode中保存key(键),value(值),freq(频度),prev(前驱),next(后继)
FreqNode中保存freq(频度)、prev(前驱)、next(后继)、first(指向最新的KeyNode),last(指向最老的KeyNode)
在数据结构LFUCache中维护如下属性:
capacity:缓存的容量
keyDict:从key到KeyNode的映射
freqDict:从freq到FreqNode的映射
head:指向最小的FreqNode
整体数据结构设计如下图所示:
head --- FreqNode1 ---- FreqNode2 ---- ... ---- FreqNodeN
| | |
first first first
| | |
KeyNodeA KeyNodeE KeyNodeG
| | |
KeyNodeB KeyNodeF KeyNodeH
| | |
KeyNodeC last KeyNodeI
| |
KeyNodeD last
|
last
LFUCache操作实现如下:
set(key, value):
如果capacity为0,忽略当前操作,结束
如果keyDict中包含key,则替换其value,更新节点频度,结束
否则,如果当前keyDict的长度 == capcity,移除head.last(频度最低且最老的KeyNode)
新增KeyNode(key, value),加入keyDict,并更新freqDict
get(key):
若keyDict中包含key,则更新节点频度,返回对应的value
否则,返回-1
节点频度的更新:
从keyDict中找到对应的KeyNode,然后通过KeyNode的freq值,从freqDict找到对应的FreqNode
如果FreqNode的next节点不等于freq + 1,则在其右侧插入一个值为freq + 1的新FreqNode节点
将KeyNode的freq值+1后,从当前KeyNode链表转移到新的FreqNode对应的KeyNode链表
如果KeyNode移动之后,原来的FreqNode对应的KeyNode链表为空,则删除原来的FreqNode
在操作完毕后如果涉及到head的变更,则更新head
简化数据结构后的方法(三个hashmap)
1 class LFUCache { 2 public: 3 LFUCache(int capacity) { 4 size = 0; 5 cap = capacity; 6 } 7 8 int get(int key) { 9 if(m.count(key) == 0) return -1;//not exist 10 fm[m[key].second].erase(mIter[key]);//erase the current object 11 m[key].second++;//update the freqence 12 fm[m[key].second].push_back(key);//add the GET object 13 mIter[key] = -- fm[m[key].second].end();//记录最后一个点 在相同的freq 14 15 if(fm[minFreq].size() == 0) minFreq++;//如果当前minFreq对应fm的list size是0 的话(因为后来get操作使其freq加一)所在的freq队列为空,则最小的freq要++ 16 return m[key].first;//return value 17 } 18 19 void put(int key, int value) { 20 if(cap <= 0) return;//capacity <= 0 end 21 int val = get(key); 22 if(val != -1){//如果存在,则update value 23 m[key].first = value; 24 return; 25 } 26 27 if(size >= cap){ 28 m.erase(fm[minFreq].front());//m中去掉最老最久的点 29 mIter.erase(fm[minFreq].front());//mIter中去除 30 fm[minFreq].pop_front();//fm中去除 31 size--; 32 } 33 34 m[key] = {value, 1}; 35 fm[1].push_back(key); 36 mIter[key] = --fm[1].end(); 37 minFreq = 1; 38 size++; 39 } 40 private: 41 int size; 42 int cap; 43 unordered_map<int, pair<int, int>> m; //key to pair(value, frequence) 44 unordered_map<int, list<int>::iterator> mIter;//key to list iterator to record the last node in the same frequence 为了erase最后一个,freq最小最老的 45 unordered_map<int, list<int>> fm;//freq to list of key 46 int minFreq; 47 }; 48 49 /** 50 * Your LFUCache object will be instantiated and called as such: 51 * LFUCache obj = new LFUCache(capacity); 52 * int param_1 = obj.get(key); 53 * obj.put(key,value); 54 */