【6】460. LFU Cache

460. LFU Cache

Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: getand 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  */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
posted @ 2017-02-02 12:05  会咬人的兔子  阅读(726)  评论(0编辑  收藏  举报