【链表-8】146.LRU缓存

发布时间:2026/10/16 6:21:34

【链表-8】146.LRU缓存 题目描述请你设计并实现一个满足 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); // 缓存是 {11} lRUCache.put(2, 2); // 缓存是 {11, 22} lRUCache.get(1); // 返回 1 lRUCache.put(3, 3); // 该操作会使得关键字 2 作废缓存是 {11, 33} lRUCache.get(2); // 返回 -1 (未找到) lRUCache.put(4, 4); // 该操作会使得关键字 1 作废缓存是 {44, 33} lRUCache.get(1); // 返回 -1 (未找到) lRUCache.get(3); // 返回 3 lRUCache.get(4); // 返回 4解题思路这题我卡在了如何维护访问顺序的问题上了。看了题解才知道其实思路不难。方法一哈希表 双向链表为什么需要两个数据结构需求数据结构原因O(1) 查找 key哈希表unordered_map查找 O(1)O(1) 维护访问顺序双向链表插入、删除、移动节点都是 O(1)双向链表的角色头部最近使用的节点尾部最久未使用的节点每次get或put把节点移到头部超出容量时删除尾部节点具体过程示例哈希表: {1: node1, 2: node2, 3: node3} 双向链表: head - 3 - 1 - 2 - tail ↑ 最近使用 最久未使用 get(1) 后: head - 1 - 3 - 2 - tail put(4) 后: 容量超出删除 tail 前一个2 head - 4 - 1 - 3 - tail代码实现class LRUCache { private: struct Node { int key, value; Node *prev, *next; Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {} }; int capacity; unordered_mapint, Node* cache; // key → 节点指针 Node *head, *tail; // 虚拟头尾节点 // 把节点移到头部最近使用 void moveToHead(Node* node) { removeNode(node); addToHead(node); } // 从链表中删除节点 void removeNode(Node* node) { node-prev-next node-next; node-next-prev node-prev; } // 把节点加到头部 void addToHead(Node* node) { node-next head-next; node-prev head; head-next-prev node; head-next node; } // 删除尾部节点最久未使用 Node* removeTail() { Node* node tail-prev; removeNode(node); return node; } public: LRUCache(int capacity) : capacity(capacity) { head new Node(0, 0); // 虚拟头结点 tail new Node(0, 0); // 虚拟尾节点 head-next tail; tail-prev head; } int get(int key) { if (cache.find(key) cache.end()) { return -1; } Node* node cache[key]; moveToHead(node); // 移到头部表示最近使用 return node-value; } void put(int key, int value) { if (cache.find(key) ! cache.end()) { // key 已存在更新值并移到头部 Node* node cache[key]; node-value value; moveToHead(node); } else { // key 不存在新建节点 Node* node new Node(key, value); cache[key] node; addToHead(node); // 超出容量删除尾部 if (cache.size() capacity) { Node* removed removeTail(); cache.erase(removed-key); delete removed; } } } };复杂度分析操作时间复杂度说明getO(1)哈希表查找 O(1) 链表移动 O(1)putO(1)哈希表插入 O(1) 链表操作 O(1)空间复杂度O(capacity)哈希表 链表最多存 capacity 个节点关键细节1. 为什么用虚拟头尾节点head new Node(0, 0); tail new Node(0, 0); head-next tail; tail-prev head;避免处理链表为空、插入第一个节点等边界情况所有节点的插入删除逻辑统一2.get和put都要调用moveToHead因为get也算使用被get的节点应该变成最近使用的。3. 超出容量时删除尾部Node* removed removeTail(); cache.erase(removed-key); delete removed;注意先从哈希表中删除再释放节点内存。4. 为什么用双向链表而不是单向链表因为需要 O(1) 删除任意节点。单向链表删除节点需要知道前驱双向链表可以直接通过node-prev找到前驱。方法二用listunordered_mapC STL 版代码实现:class LRUCache { private: int capacity; listpairint, int cacheList; // 存 (key, value) unordered_mapint, listpairint, int::iterator cacheMap; public: LRUCache(int capacity) : capacity(capacity) {} int get(int key) { if (cacheMap.find(key) cacheMap.end()) return -1; // 移到头部 cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]); return cacheMap[key]-second; } void put(int key, int value) { if (cacheMap.find(key) ! cacheMap.end()) { // 更新值并移到头部 cacheMap[key]-second value; cacheList.splice(cacheList.begin(), cacheList, cacheMap[key]); } else { // 插入新节点 cacheList.push_front({key, value}); cacheMap[key] cacheList.begin(); // 超出容量 if (cacheList.size() capacity) { cacheMap.erase(cacheList.back().first); cacheList.pop_back(); } } } };优点用 STL 的list和splice代码更简洁缺点需要理解splice的用法两种方法对比:方法getput代码复杂度推荐度手写双向链表 哈希表O(1)O(1)较高⭐⭐⭐⭐⭐STL list 哈希表O(1)O(1)中等⭐⭐⭐⭐总结要点说明核心思想哈希表 双向链表哈希表作用O(1) 查找 key双向链表作用O(1) 维护访问顺序头部最近使用尾部最久未使用超出容量删除尾部节点这道题是设计题的经典题也是面试高频题。核心是哈希表 双向链表的组合。
延伸阅读

更多相关文章

2026/10/16 6:16:34

2S锂电池主动均衡实战:基于MP2642与STM32的双向能量转移方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 6:16:34

MP2642GR-Z与PIC18F47J53协同实现2S电池动态均衡方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 7:42:01

基于Vue+SpringBoot+Node.js的评奖评优管理系统全栈实践

这不是我第一次做高校业务系统,但每次遇到“评奖评优”这类流程型需求,还是会格外谨慎。原因很简单:表面上它只是“学生提交申请、老师审核、名单公示”三步,实际上背后牵涉多角色协作、材料完整性、量化打分、状态变更留痕、批次…

2026/10/16 7:42:01

外卖点评系统核心设计:维度拆解、评分聚合与异常识别

1. 系统设计与需求拆解1.1 外卖点评和到店点评的本质差异说到点评系统,很多人第一反应是照搬到店评价那一套:打分、写文字、传图片。但真正在外卖场景里跑过一遍就会明白,两者面对的完全是两种信任模型。到店评价建立在“我亲眼看到了环境、摸…

2026/10/16 7:42:01

Python文件读取:read()、readline()、readlines()的区别与选型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 7:42:01

Flink Exactly Once 实现原理与生产落地四大关键

1. 为什么“Exactly Once”不是一句口号,而是Flink工程落地的生死线在某次跨部门数据对账会上,A同学指着大屏上实时订单金额与T1离线报表的3.7%偏差,语气平静但问题尖锐:“上游说Flink保证Exactly Once,下游说Kafka消费…

2026/10/16 7:37:00

考研408应用层协议高频考点与复习方法全解析

1. 应用层到底在考什么:先搞清楚这章的分值分布很多人复习到应用层就开始飘了,觉得前面传输层、网络层那么难都啃下来了,应用层不就是几个协议背一背的事吗。我当年第一次做真题也是这个心态,结果选择题连着错三道,大题…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/16 0:01:11

Cephadm实战指南:Linux系统工程师的生产级Ceph集群部署与排障

简介:本资源是一份完整的Ceph分布式存储系统中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,帮助读者系统掌握Ceph集群的部署、配置与运维核心能力。文档覆盖对象存储(RGW)、块设备&#xf…

2026/10/16 0:01:11

ITK中SpatialObject几何体系解析:坐标系、变换与管道集成

1. 空间对象的定位:为什么 ITK 需要一套几何描述体系很多刚开始接触 ITK 的朋友,包括我自己,最初都是被它的图像滤波、配准、分割这些“正统”功能吸引进来的。等真正上手做点实际项目,比如把分割出来的器官边界做量化分析、给血管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑