之後將開通新的欄目《面試高頻算法詳解》,爲你們介紹一些比較常考的稍微複雜一點的算法題,有興趣的能夠點贊關注加轉發呀~面試
圖源: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值對應於雙向鏈表中的節點地址。
每當執行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算法有比較明確的瞭解。建議充分理解。
本文爲來源業餘碼農,轉載請聯繫本公衆號得到受權。
推薦閱讀(點擊下方連接便可閱讀)
建議簡歷寫很差的同窗進來瞧一瞧~
瞭解這些C++經常使用庫,或許可以幫你找到合適的我的項目!
生物專業卻能簽約字節跳動,在大學期間他經歷了什麼
生物專業女生教你準備兩個月簽約AI獨角獸
想成爲BAT後臺開發工程師,這些是基礎!
Amazing10承蒙厚愛。