161 lines
3.9 KiB
Markdown
161 lines
3.9 KiB
Markdown
# LRU 缓存
|
|
|
|
## 题目
|
|
|
|
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
|
|
实现 LRUCache 类:
|
|
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
|
|
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
|
|
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
|
|
函数 get 和 put 必须以 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 <= 105
|
|
最多调用 2 * 105 次 get 和 put
|
|
|
|
## 分析
|
|
|
|
- 核心数据结构
|
|
- ListNode
|
|
- prev
|
|
- next
|
|
- ind
|
|
- val
|
|
- HashMap<Integer, ListNode>
|
|
- ind
|
|
- ListNode
|
|
- 链表
|
|
- head
|
|
- tail
|
|
- 核心字段
|
|
- size
|
|
- 当前大小
|
|
- cap
|
|
- 最大容量
|
|
|
|
## 代码
|
|
|
|
```java
|
|
class LRUCache {
|
|
|
|
// Init 1: ListNode
|
|
class ListNode {
|
|
int key;
|
|
int val;
|
|
ListNode next;
|
|
ListNode prev;
|
|
|
|
public ListNode () { }
|
|
public ListNode (int key, int val) {
|
|
this.key = key;
|
|
this.val = val;
|
|
}
|
|
}
|
|
|
|
ListNode dummyHead = new ListNode(0, 0);
|
|
ListNode dummyTail = new ListNode(0, 0);
|
|
|
|
|
|
|
|
// Init 2: HashMap
|
|
Map<Integer, ListNode> map = new HashMap<>();
|
|
|
|
// Init 3: Other
|
|
private int size;
|
|
private int cap;
|
|
|
|
public LRUCache(int capacity) {
|
|
this.cap = capacity;
|
|
this.size = 0;
|
|
dummyHead.next = dummyTail;
|
|
dummyTail.prev = dummyHead;
|
|
}
|
|
|
|
public int get(int key) {
|
|
ListNode node = map.get(key);
|
|
if (node == null) {
|
|
return -1;
|
|
}
|
|
// Operation: Change Loc
|
|
moveToHead(node);
|
|
return node.val;
|
|
}
|
|
|
|
public void put(int key, int value) {
|
|
ListNode node = map.get(key);
|
|
if (node == null) {
|
|
ListNode newNode = new ListNode(key, value);
|
|
addToHead(newNode);
|
|
map.put(key, newNode);
|
|
size++;
|
|
if (size > cap) {
|
|
ListNode tail = removeTail();
|
|
map.remove(tail.key);
|
|
size--;
|
|
}
|
|
} else {
|
|
node.val = value;
|
|
moveToHead(node);
|
|
}
|
|
}
|
|
|
|
// Typical Methods
|
|
// 1. addToHead
|
|
private void addToHead(ListNode node) {
|
|
node.next = dummyHead.next;
|
|
node.prev = dummyHead;
|
|
dummyHead.next.prev = node;
|
|
dummyHead.next = node;
|
|
|
|
}
|
|
// 2. removeNode
|
|
private void removeNode(ListNode node) {
|
|
node.prev.next = node.next;
|
|
node.next.prev = node.prev;
|
|
}
|
|
// 3. moveToHead
|
|
private void moveToHead(ListNode node) {
|
|
removeNode(node);
|
|
addToHead(node);
|
|
}
|
|
// 4. removeTail
|
|
private ListNode removeTail() {
|
|
ListNode res = dummyTail.prev;
|
|
removeNode(res);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
*/
|
|
``` |