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