📘题目描述
请你设计并实现一个满足 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
💡解题思路
LRU 缓存的核心要求:
-
访问过的数据必须移到最前面
-
超出容量时,需要淘汰最久未访问的数据(尾部)
✅数据结构选择:OrderedDict
(有序字典)
Python 的 collections.OrderedDict
是标准库中专为 LRU/缓存设计的容器,具备以下特性:
-
保持插入顺序
-
可用
move_to_end(key)
将元素移到末尾(表示最近使用) -
使用
popitem(last=False)
可以O(1) 删除最久未使用的元素(头部)
✅Python 实现
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.cache = OrderedDict() # 初始化有序字典
self.capacity = capacity # 最大容量
def get(self, key: int) -> int:
if key not in self.cache:
return -1 # 不存在返回 -1
# 将访问的元素移到末尾(表示最近使用)
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key: int, value: int) -> None:
if key in self.cache:
# 移到末尾,准备更新
self.cache.move_to_end(key)
self.cache[key] = value
# 超出容量,弹出最久未使用的元素
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # FIFO 移除头部元素
⏱️时间与空间复杂度
操作 | 时间复杂度 | 空间复杂度 |
---|---|---|
get | O(1) | O(capacity) |
put | O(1) | O(capacity) |
🧱易错点总结
易错点 | 正确做法说明 |
---|---|
更新数据未移动位置 | move_to_end(key) 保证“最近使用” |
put 超过容量未淘汰 | 使用 popitem(last=False) 删除头部 |
get 不存在时返回错误 | 返回 -1 |
🎯总结
-
使用
OrderedDict
是 Python 实现 LRU 的最简洁且效率最高的方式。 -
同时掌握 哈希表 + 双向链表 手动实现 LRU 也是面试时加分项。
-
本题考查的是缓存机制 + 数据结构的组合使用,属于高频面试题之一。
🔄补充拓展:不允许使用 OrderedDict
?
如果面试官要求不能使用内置容器,可使用:
-
哈希表 + 双向链表手动实现
-
哈希表存储 key → Node 映射
-
双向链表维护使用顺序:最近使用的节点放在头部,最久未使用的节点放在尾部
🔨 LRU Cache:哈希表 + 双向链表 手动实现版
🎯核心思路
-
用 双向链表维护使用顺序(头:最近使用,尾:最久未使用)
-
用 哈希表实现快速查找 key → 节点(O(1))
✅ Python 代码(不依赖任何标准库)
class Node:
def __init__(self, key: int, value: int):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {} # key -> Node
# 使用两个哨兵节点,head.next 是最常用,tail.prev 是最少用
self.head = Node(0, 0)
self.tail = Node(0, 0)
self.head.next = self.tail
self.tail.prev = self.head
# 内部方法:将节点移动到头部(表示最近使用)
def _move_to_head(self, node: Node):
self._remove(node)
self._add(node)
# 内部方法:添加节点到头部
def _add(self, node: Node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
# 内部方法:移除链表中的节点
def _remove(self, node: Node):
prev = node.prev
nxt = node.next
prev.next = nxt
nxt.prev = prev
def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache[key]
self._move_to_head(node) # 使用了就移到头部
return node.value
def put(self, key: int, value: int) -> None:
if key in self.cache:
node = self.cache[key]
node.value = value # 更新值
self._move_to_head(node)
else:
node = Node(key, value)
self.cache[key] = node
self._add(node)
if len(self.cache) > self.capacity:
# 淘汰尾部最久未使用节点
lru = self.tail.prev
self._remove(lru)
del self.cache[lru.key]
🧪使用示例
lru = LRUCache(2)
lru.put(1, 1)
lru.put(2, 2)
print(lru.get(1)) # 输出 1
lru.put(3, 3) # 淘汰 key=2
print(lru.get(2)) # 输出 -1
lru.put(4, 4) # 淘汰 key=1
print(lru.get(1)) # 输出 -1
print(lru.get(3)) # 输出 3
print(lru.get(4)) # 输出 4
时间和空间复杂度与使用OrderedDict方法相同。
🧱易错点总结
错误点 | 正确方式 |
---|---|
删除时未断开前后节点 | _remove() 正确处理 prev/next |
忘记从 cache 删除淘汰节点 | del self.cache[lru.key] |
双向链表头尾更新顺序错误 | 始终更新 prev/next 的双向性 |