算法 - LRU 缓存机制 (LRU Cache) ⭐全栈必考
1. 题目
设计并实现一个 LRU(Least Recently Used,最近最少使用)缓存,支持 get 和 put 操作,要求两者都是 O(1) 时间复杂度。
- 构造时给定正整数
capacity表示缓存容量。 get(key):存在则返回 value,否则返回-1。put(key, value):写入数据。若写入导致容量超限,应驱逐最久未使用的键。- 关键:
get和put都要更新「使用顺序」,被访问的键变成「最近使用」。
示例:
1 | LRUCache cache = new LRUCache(2); |
2. 解题思路
LRU 的两大需求互相矛盾:
- O(1) 按 key 查找 —— 需要哈希表。
- O(1) 维护使用顺序、淘汰尾部 —— 需要能在头部/尾部任意增删的有序结构。
答案:哈希表 + 双向链表。这正是浏览器缓存、Redis allkeys-lru、各类内存缓存的标准实现。
- 双向链表:越靠近 head 越「新」,越靠近 tail 越「旧」。淘汰时直接删 tail 前一个。
- 哈希表:
key -> 链表节点,实现 O(1) 定位到某个节点。 - 命中或写入后,把该节点用
addToHead(先remove再插入头部)移到最前。
为什么是双向而非单向?因为删除任意节点时需要 O(1) 拿到它的前驱,单向链表做不到。
巧用语言特性:JavaScript 的
Map本身就维护插入顺序,把 key 重新删除再插入等价于「移到最新」,可以极简实现。面试时建议两种都讲:Map版展示语言理解,手写双向链表版展示数据结构功底。
3. TypeScript 实现
3.1 极简版:利用 Map 的插入有序性
1 | class LRUCache { |
3.2 经典版:哈希表 + 手写双向链表
1 | interface DLinkedNode { |
- 时间复杂度:
get/put均为O(1)。 - 空间复杂度:
O(capacity)。
4. 面试延伸
- LFU 缓存(LeetCode 460) 是 LRU 的进阶,需要频率桶 + 双向链表,更烧脑。
- 真实工程:Redis 用近似 LRU(随机采样淘汰)省内存;前端
keep-alive、图片缓存、请求缓存都用 LRU 思想。 - 讲题要点:先说清「为什么单结构做不到 O(1)」,再自然引出「哈希 + 双向链表」的组合,展示的是权衡能力而非背代码。
难度:中等 | LeetCode 146 题 | 全栈数据结构压轴题

