146. LRU缓存机制
查看题目中等
设计
哈希表
链表
双向链表
频率: 818
解法一:lru-cache
时间复杂度:O(1) | 空间复杂度:O(capacity) | 推荐使用
动画演示
当前操作: 无
LRU缓存可视化
容量: 3 | 当前大小: 0
Head
缓存为空
Tail
操作日志
代码实现
class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key;
value = _value;
}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
} else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
时间复杂度:O(1)
空间复杂度:O(capacity)
算法思路:
LRU (Least Recently Used) 缓存是一种常用的缓存淘汰策略,当缓存容量达到上限时,删除最久未使用的数据。
核心思想:
1. 使用双向链表维护访问顺序
- 链表头部:最近访问的元素
- 链表尾部:最久未访问的元素
- 每次访问或更新时,将节点移到头部
2. 使用哈希表实现 O(1) 查找
- key: 缓存的键
- value: 对应的双向链表节点
- 通过哈希表快速定位节点位置
3. 关键操作:
- get(key): 通过哈希表定位节点,将其移到链表头部
- put(key, value):
- 如果key存在,更新值并移到头部
- 如果key不存在且容量未满,创建新节点并添加到头部
- 如果key不存在且容量已满,删除尾部节点,创建新节点并添加到头部
复杂度分析:
- 时间复杂度:O(1)
- get操作:哈希表查找O(1) + 双向链表移动O(1) = O(1)
- put操作:哈希表查找O(1) + 双向链表插入/删除O(1) = O(1)
- 空间复杂度:O(capacity)
- 哈希表存储最多capacity个键值对
- 双向链表存储最多capacity个节点