[LeetCode] LFU Cache 最近最不常用頁面置換緩存器

[LeetCode] LFU Cache 最近最不常用頁面置換緩存器

 

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 (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被使用的次數最少,只使用了一次。相信這個簡單的例子可以大概說明二者的區別。

這道題比之前那道LRU的題目還要麻煩一些,因爲那道題只要用個list把數字按時間順序存入,鏈表底部的位置總是最久未被使用的,每次刪除底部的值即可。而這道題不一樣,由於需要刪除最少次數的數字,那麼我們必須要統計每一個key出現的次數,所以我們用一個哈希表m來記錄當前數據{key, value}和其出現次數之間的映射,這樣還不夠,爲了方便操作,我們需要把相同頻率的key都放到一個list中,那麼需要另一個哈希表freq來建立頻率和一個裏面所有key都是當前頻率的list之間的映射。由於題目中要我們在O(1)的時間內完成操作了,爲了快速的定位freq中key的位置,我們再用一個哈希表iter來建立key和freq中key的位置之間的映射。最後當然我們還需要兩個變量cap和minFreq,分別來保存cache的大小,和當前最小的頻率。

爲了更好的講解思路,我們還是用例子來說明吧,我們假設cache的大小爲2,假設我們已經按順序put進去5,4,那麼來看一下內部的數據是怎麼保存的,由於value的值並不是很重要,爲了不影響key和frequence,我們採用value#來標記:

m:

5 -> {value5, 1}

4 -> {value4, 1}

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;
};

複製代碼

 

類似題目:

LRU Cache

 

參考資料:

https://leetcode.com/problems/lfu-cache/

https://discuss.leetcode.com/topic/69436/concise-c-o-1-solution-using-3-hash-maps-with-explanation

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章