🎯 一、为什么要掌握 LRU 算法?
-
是前端/全栈/后端通吃的经典算法题
-
实际应用场景广泛:页面缓存、图片懒加载缓存、React Fiber 中的任务优先队列
-
Map + 双向链表是核心实现方式(JS 可简化)
-
面试官借此考察你对:
- 数据结构选择
- 性能复杂度分析
- 手写能力的掌握
📌 二、什么是 LRU?
最近最少使用(Least Recently Used)缓存策略: 保持固定容量,最近访问的数据会优先保留,最久未使用的被淘汰。
🎯 三、功能需求(面试通常要求)
-
固定容量
capacity -
支持:
get(key):如果存在则返回值,并更新访问顺序put(key, value):如果超出容量,则删除最久未使用项
-
要求:所有操作时间复杂度 O(1)
✍️ 四、用 JS Map 手写简版(🔥推荐:简洁 + 面试可讲)
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map(); // 保证插入顺序
}
get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
// 先删掉旧位置,再插入到末尾(表示最近使用)
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key); // 删除旧位置
} else if (this.cache.size >= this.capacity) {
// 删除最旧的(Map 的第一个 key)
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, value);
}
}
✅ 五、验证用例
const lru = new LRUCache(2);
lru.put(1, 'A');
lru.put(2, 'B');
console.log(lru.get(1)); // A
lru.put(3, 'C'); // 淘汰 key=2
console.log(lru.get(2)); // -1(不存在)
console.log(lru.get(3)); // C
🧠 六、核心原理图(Map版)
Map 顺序:按访问时间更新(靠后 = 最近使用)
初始:
cache = {1: 'A', 2: 'B'} → put(3, 'C') 淘汰 1
↑ oldest
🔁 七、双向链表版本(深入 + 真正 O(1) 实现)
当面试官要求不能用 Map,你需要自己实现哈希表 + 双向链表
- 哈希表(对象)存储 key → node 的映射
- 双向链表维护访问顺序(head=最常用,tail=最少用)
class Node {
constructor(key, value) {
this.key = key;
this.value = value;
this.prev = this.next = null;
}
}
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map();
// 初始化双向链表的伪头尾节点
this.head = new Node(null, null);
this.tail = new Node(null, null);
this.head.next = this.tail;
this.tail.prev = this.head;
}
_add(node) {
node.next = this.head.next;
node.prev = this.head;
this.head.next.prev = node;
this.head.next = node;
}
_remove(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
get(key) {
const node = this.map.get(key);
if (!node) return -1;
this._remove(node);
this._add(node);
return node.value;
}
put(key, value) {
if (this.map.has(key)) {
this._remove(this.map.get(key));
}
const node = new Node(key, value);
this._add(node);
this.map.set(key, node);
if (this.map.size > this.capacity) {
const lru = this.tail.prev;
this._remove(lru);
this.map.delete(lru.key);
}
}
}
❗ 八、常见面试陷阱
| 问题 | 正解 |
|---|---|
| get(key) 要更新顺序吗? | 是,必须移到最近使用处 |
| 超出容量后谁被淘汰? | 最久未使用(队尾) |
| 为啥时间复杂度是 O(1)? | Map + 链表指针插入/删除都是 O(1) |
| Map 本身会排序吗? | 是的,按照插入顺序记录(用于简化版) |