因为dict无法在O(1)内移动键序,LRU需双向链表管时序、哈希表管查找;__init__须初始化capacity、cache(key→节点)、head/tail哑节点;get需查+移至头;put分存在/未满/已满三类处理,删LRU时须先删字典再删节点。

为什么不能只用 dict 实现 LRU Cache?
因为 dict 在 Python 3.7+ 虽然保持插入顺序,但无法在 O(1) 时间内把任意一个已存在的键“移到末尾”——pop(key) + update({key: value}) 是 O(n),且会触发哈希表重建和键重散列。LRU 的核心操作(访问命中时更新顺序、容量满时淘汰最久未用)必须全部是 O(1),否则就不是真正的 LRU Cache。
所以得靠双向链表管理访问时序,靠哈希表实现 O(1) 查找——两者缺一不可。
__init__ 里必须初始化哪些结构?
需要三个关键成员:
-
self.capacity:整数,缓存最大容量 -
self.cache:字典,映射key → ListNode(不是值!是链表节点引用) -
self.head和self.tail:哑节点(dummy node),用于简化边界操作;head.next指向最近使用(MRU),tail.prev指向最久未使用(LRU)
注意:不要在初始化时创建真实数据节点,只建两个哑节点并互相连接:head.next = tail,tail.prev = head。否则后续 get/put 逻辑容易错位。
立即学习“Python免费学习笔记(深入)”;
如何让 get 同时完成查找 + 提升优先级?
查到后不能只返回值,必须把对应节点从原位置摘下,再插到 head 后面(即 MRU 位置)。这个“摘下 + 插入”必须是纯指针操作,不涉及内存分配或遍历。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
关键步骤(假设 node 是查到的节点):
- 调用
self._remove(node):断开其前后指针(node.prev.next = node.next,node.next.prev = node.prev) - 调用
self._add_to_head(node):把node插到head和head.next之间 - 最后返回
node.value
如果没查到,直接 return -1,不做任何链表操作。
put 时怎么处理已存在 key 和容量溢出?
分三类情况,顺序不能乱:
- 如果
key已存在:先用self._remove(cache[key])把旧节点摘掉,再新建节点(或复用)并_add_to_head,最后更新self.cache[key]指向新节点 - 如果不存在且未满:新建节点,
_add_to_head,存入self.cache - 如果不存在且已满:先删掉
tail.prev(即 LRU 节点),从self.cache中del对应 key;再新建节点、插入、更新字典
特别注意:删除 LRU 节点时,必须先从字典中 del self.cache[tail.prev.key],再调用 self._remove(tail.prev),否则后续 get 可能拿到已失效的节点引用。
双向链表节点类本身只需四个属性:key、value、prev、next;所有操作都围绕这四个字段的指针改写。最容易漏的是某个方向的指针没更新(比如只改了 prev 没改 next),导致链表断裂或循环引用。

















