本文基於LeetCode第146. LRU 緩存機制進行實現。
題目
運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制 。
實現 LRUCache 類:
- LRUCache(int capacity) 以正整數作爲容量 capacity 初始化 LRU 緩存
- int get(int key) 如果關鍵字 key 存在於緩存中,則返回關鍵字的值,否則返回 -1 。
- void put(int key, int value) 如果關鍵字已經存在,則變更其數據值;如果關鍵字不存在,則插入該組「關鍵字-值」。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而爲新的數據值留出空間。
進階:你是否可以在 O(1) 時間複雜度內完成這兩種操作?
示例:
輸入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
輸出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解釋
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 緩存是 {1=1}
lRUCache.put(2, 2); // 緩存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 該操作會使得關鍵字 2 作廢,緩存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 該操作會使得關鍵字 1 作廢,緩存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
- 1 <= capacity <= 3000
- 0 <= key <= 10000
- 0 <= value <= 10 ^ 5
- 最多調用 2 * 10 ^ 5 次 get 和 put
分析
LRU (最近最少使用) ,一種數據淘汰策略算法,在頁面置換、Redis數據淘汰中都存在這種算法。實現的思路也比較簡單,使用雙向鏈表 + 哈希表。
哈希表在這裏的作用主要是爲了實現O(1)複雜度獲取到對應的node。我們只需要在訪問節點後,把該節點移到鏈表的頭部,保證雙向鏈表的尾部是最久未訪問的數據即可。當緩存的容量達到上限時,只需要把雙向鏈表尾部的節點移除即可。
編碼
class LRUCache {
// 雙向鏈表 + HashMap
private Node head;
private Node tail;
// key->node,實現O(1)複雜度獲取node
private HashMap<Integer, Node> map;
private int maxSize;
private class Node {
Node pre;
Node next;
Integer key;
Integer value;
public Node(Integer key, Integer value) {
this.key = key;
this.value = value;
}
}
public LRUCache(int capacity) {
this.maxSize = capacity;
head = new Node(null, null);
tail = new Node(null, null);
head.next = tail;
tail.pre = head;
map = new HashMap<>(capacity);
}
public int get(int key) {
// 沒有命中,返回-1
if (!map.containsKey(key)) {
return -1;
}
// 命中後,需要把對應的node移到鏈表的頭部,保證鏈表的尾部是最久未訪問的
Node node = map.get(key);
unlink(node);
appendHead(node);
return node.value;
}
public void put(int key, int value) {
// 如果緩存已經存在,則刪除原來的
if (map.containsKey(key)) {
Node node = map.get(key);
unlink(node);
} else {
// 如果容量達到上限,需要先移除鏈表尾的節點,再插入新的節點
if (map.size() >= maxSize) {
Node removeNode = removeTail();
map.remove(removeNode.key);
}
}
// 插入節點到鏈表頭部
Node node = new Node(key, value);
map.put(key, node);
appendHead(node);
}
/**
* 釋放node節點
*/
private void unlink(Node node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
node.pre = null;
node.next = null;
}
/**
* 將node節點插入鏈表頭部
*/
private void appendHead(Node node) {
Node next = head.next;
node.next = next;
next.pre = node;
node.pre = head;
head.next = node;
}
/**
* 刪除鏈表尾節點
*/
private Node removeTail() {
Node node = tail.pre;
Node pre = node.pre;
pre.next = tail;
tail.pre = pre;
node.next = null;
node.pre = null;
return node;
}
}
/**
* 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);
*/
這個擊敗率感覺還不如調api來滴實在啊。