面試高頻算法詳解-LRU

之後將開通新的欄目《面試高頻算法詳解》,爲你們介紹一些比較常考的稍微複雜一點的算法題,有興趣的能夠點贊關注加轉發呀~面試

面試高頻算法詳解-LRU

圖源:pexels算法

01
題目介紹緩存

題目描述:
leetcode 146 LRU緩存機制中等難度數據結構

運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制。它應該支持如下操做:獲取數據 get 和寫入數據 put 。ide

獲取數據 get(key) - 若是密鑰 (key) 存在於緩存中,則獲取密鑰的值(老是正數),不然返回 -1。
寫入數據 put(key, value) - 若是密鑰不存在,則寫入其數據值。當緩存容量達到上限時,它應該在寫入新數據以前刪除最近最少使用的數據值,從而爲新的數據值留出空間。設計

要求: O(1) 時間複雜度完成這兩種操做code

02
題目分析blog

概念
LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。ci

重點
1 最近被訪問的數據,其優先級最高;
2 優先級低的數據最早被清除;leetcode

時間複雜度
O(1)

03
可行方案

1 鏈表結構

使用鏈表結構保存緩存數據。
每當執行put操做時,遍歷鏈表判斷該數據是否爲新數據:
若爲新數據,則在鏈表頭部新建節點並存放新數據;當鏈表長度超過緩存大小時,將鏈表尾部節點刪除。
若爲舊數據,則說明緩存數據命中,更新該緩存數據,並將命中的鏈表節點移到鏈表頭部。

每當執行get操做時,經過遍歷鏈表進行緩存數據的尋找:
若命中,則根據密鑰(key)返回數據值(value),並將數據所在的鏈表節點置於鏈表頭部;
若未命中,則說明該數據不在緩存中,返回-1。

問題:鏈表在使用的時候,爲了肯定是否命中,須要對鏈表結構進行遍歷。時間複雜度爲o(n),n爲鏈表長度。未知足題目要求。

2 雙向鏈表與哈希表結合

利用雙向鏈表保存緩存數據,利用哈希表解決須要遍歷尋找命中的問題。
雙向鏈表中存放的是緩存數據;哈希表中的value值對應於雙向鏈表中的節點地址。

面試高頻算法詳解-LRU

每當執行put操做時,先判斷插入的鍵值對中的key是否存在與哈希表中:
若key已經存在,說明該數據命中緩存,則根據key對應的節點地址找到該緩存數據節點,更新該節點的數據值,並將該節點置於雙向鏈表的頭部,同時更新key所對應的節點地址。
若key不存在,說明該數據在緩存中未發生命中,則在雙向鏈表頭部建立新的節點存放新的數據,並在哈希表中添加新的key值與鏈表頭部地址相對應。若鏈表長度大於緩存大小,則刪除鏈表尾部節點以及對應的哈希表中的鍵值對。

每當執行get操做時,先判斷插入的的鍵值對中的key是否存在與哈希表中:
若key已經存在,則可經過key值對應的鏈表中節點的地址,就可取得緩存數據;同時將該節點置於鏈表的頭部並更新key對應的節點地址。
若對應的key不存在於哈希表中,即未發生命中,返回-1。

04
最終實現

說明

list 是C++ STL中容器,底層實現爲雙向循環鏈表,任意位置插入和刪除時間複雜度0(1)。
unordered_map 同爲C++ STL中容器,底層實現爲哈希表。

C++代碼:
熟悉其它語言的同窗也可看看,理解其中算法思想。

class LRUCache {
public:
    int size;
    list<pair<int, int>> cache; 
    unordered_map<int, list<pair<int,int>>::iterator> map;

    LRUCache(int capacity) {
        size = capacity;
    }

    int get(int key) {
        auto it = map.find(key);  
        if(it == map.end())  //判斷key是否存在於哈希表中
            return -1;      
        auto temp = *map[key];  
        cache.erase(map[key]);  //刪除命中節點
        cache.push_front(temp);  //在鏈表頭部建立新的數據節點 
        map[key] = cache.begin();  //更新key所對應的節點地址
        return temp.second;         
    }

    void put(int key, int value) {
        auto it = map.find(key);
        if(it == map.end())
        {
            if(cache.size()==size)  //若緩存已滿
            {
                auto temp = cache.back();  //得到鏈表尾部節點
                map.erase(temp.first);  //刪除尾部節點對應哈希表鍵值對
                cache.pop_back();  //刪除尾部節點
            }        
            cache.push_front(make_pair(key,value));  //在鏈表頭部插入新的數據節點
            map[key] = cache.begin();  //更新key值對應的節點地址,指向鏈表頭部
        }
        else
        {       
            cache.erase(map[key]);
            cache.push_front(make_pair(key,value));
            map[key] = cache.begin();
        }
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

評析:
這種方案的實現其實是最簡單的一種LRU思想的表現,可是其利用效率不高。在某些狀況下,會致使在重複位置的插入和刪除,致使更新效率低下;同時因爲哈希表自己的結構也會致使其插入和查詢的效率不穩定。不過理解上述的實現可以對數據結構的結合和LRU算法有比較明確的瞭解。建議充分理解。

本文爲來源業餘碼農,轉載請聯繫本公衆號得到受權。

面試高頻算法詳解-LRU

推薦閱讀(點擊下方連接便可閱讀)

建議簡歷寫很差的同窗進來瞧一瞧~
瞭解這些C++經常使用庫,或許可以幫你找到合適的我的項目!
生物專業卻能簽約字節跳動,在大學期間他經歷了什麼
生物專業女生教你準備兩個月簽約AI獨角獸
想成爲BAT後臺開發工程師,這些是基礎!

Amazing10承蒙厚愛。

相關文章
相關標籤/搜索