当前位置: 首页 > news >正文

【LeetCode】146. LRU 缓存

题目描述

【LeetCode】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 ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 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

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105getput

核心思路

LRU的核心是淘汰最久未使用的数据,需要高效实现两个操作:

  1. 快速访问数据(get 操作);
  2. 快速插入/更新数据,并在满容量时快速删除最久未使用的数据(put 操作)。

最优方案是哈希表 + 双向链表的结合:

  • 双向链表:维护数据的使用顺序,最近使用的放在头部,最久未使用的放在尾部
  • 哈希表:键为缓存的key,值为双向链表的节点,实现O(1)时间查找节点。

代码实现

class LRUCache {// 双向链表节点:存储key、value,以及前后指针class Node {int key;int value;Node prev;Node next;public Node(int key, int value) {this.key = key;this.value = value;}}private int capacity;  // 缓存容量private Map<Integer, Node> cache;  // 哈希表:key -> 节点private Node head;  // 哨兵头节点(最近使用的节点在头部附近)private Node tail;  // 哨兵尾节点(最久未使用的节点在尾部)public LRUCache(int capacity) {this.capacity = capacity;cache = new HashMap<>(capacity);// 初始化哨兵节点,简化边界处理head = new Node(-1, -1);tail = new Node(-1, -1);head.next = tail;tail.prev = head;}public int get(int key) {if (!cache.containsKey(key)) {return -1;  // 键不存在,返回-1}// 键存在:获取节点,移动到头部(标记为最近使用)Node node = cache.get(key);moveToHead(node);return node.value;}public void put(int key, int value) {if (cache.containsKey(key)) {// 键存在:更新值,移动到头部(标记为最近使用)Node node = cache.get(key);node.value = value;moveToHead(node);return;}// 键不存在:检查容量if (cache.size() == capacity) {// 容量满:删除最久未使用的节点(尾节点的前一个)Node oldest = tail.prev;removeNode(oldest);cache.remove(oldest.key);  // 哈希表同步删除}// 插入新节点:添加到头部,哈希表记录Node newNode = new Node(key, value);addToHead(newNode);cache.put(key, newNode);}// 辅助方法:将节点移动到头部(先删除再添加到头部)private void moveToHead(Node node) {removeNode(node);addToHead(node);}// 辅助方法:删除节点private void removeNode(Node node) {node.prev.next = node.next;node.next.prev = node.prev;}// 辅助方法:将节点添加到头部(head的后面)private void addToHead(Node node) {node.prev = head;node.next = head.next;head.next.prev = node;head.next = node;}
}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/

算法详解

1. 数据结构设计

  • 双向链表:每个节点包含 keyvalueprev(前指针)、next(后指针)。通过链表维护使用顺序:
    • 最近使用的节点靠近 head(头哨兵);
    • 最久未使用的节点靠近 tail(尾哨兵)。
  • 哨兵节点headtail 不存储实际数据,用于简化边界处理(如空链表、删除首/尾节点)。
  • 哈希表cache 映射 key 到链表节点,实现O(1)时间查找。

2. 核心操作解析

get(key) 操作
  1. key 不在哈希表中,返回 -1
  2. key 存在,通过哈希表找到对应节点;
  3. 调用 moveToHead(node) 将节点移动到链表头部(标记为“最近使用”);
  4. 返回节点的 value
put(key, value) 操作
  • 键已存在
    1. 找到对应节点,更新 value
    2. 调用 moveToHead(node) 标记为“最近使用”。
  • 键不存在
    1. 若缓存满(cache.size() == capacity):
      • 找到最久未使用的节点(tail.prev);
      • 调用 removeNode(oldest) 从链表中删除;
      • 从哈希表中删除该节点的 key
    2. 创建新节点,调用 addToHead(newNode) 添加到链表头部;
    3. 将新节点加入哈希表。

3. 辅助方法作用

  • removeNode(node):从链表中删除指定节点(调整前后节点的指针)。
  • addToHead(node):将节点添加到 head 后面(成为“最新使用”的节点)。
  • moveToHead(node):先删除节点,再添加到头部(更新使用顺序)。

复杂度分析

  • 时间复杂度getput 操作均为 O(1)。哈希表查找是O(1),双向链表的插入/删除操作也是O(1)。
  • 空间复杂度O(capacity)。最多存储 capacity 个节点,哈希表和链表的空间均与容量成正比。

示例演示

capacity = 2 为例:

  1. put(1, 1) → 缓存:{1:1}(链表:head <-> 1 <-> tail);
  2. put(2, 2) → 缓存:{1:1, 2:2}(链表:head <-> 2 <-> 1 <-> tail);
  3. get(1) → 返回1,链表更新为:head <-> 1 <-> 2 <-> tail(1变为最近使用);
  4. put(3, 3) → 容量满,删除最久未使用的2,缓存:{1:1, 3:3}(链表:head <-> 3 <-> 1 <-> tail);
  5. get(2) → 返回-1(已被删除)。
http://www.dtcms.com/a/477552.html

相关文章:

  • Linux Cgroup与Device Whitelist详解
  • 恶意代码防范技术与原理(二)
  • Facebook广告投放:地域定向流量不精准?x个优化指南
  • 【Linux指令 (三)】从理解到熟悉:探索Linux底层逻辑与指令的高效之道,理解Linux系统理论核心概念与基础指令
  • 2025年10月实时最新获取地图边界数据方法,省市区县街道多级联动【文末附实时geoJson数据下载】
  • 基于单片机的燃气热水器智能控制系统设计
  • 江苏省建设厅网站怎么登不上html网页代码编辑器
  • 云服务器怎么架设网站wordpress删除月份归档
  • go语言返回值 于defer的特殊原理
  • 《线性代数》---大学数学基础课程
  • 【Go】---流程控制语句
  • Go小白学习路线
  • CMP (类Cloudera) CDP7.3(400次编译)在华为鲲鹏Aarch64(ARM)信创环境中的性能测试过程及命令
  • [GO]什么是热重载,如何使用Air工具
  • 福州网站建设公司哪个好济南工程建设验收公示网
  • 百度爱采购服务商查询丽水网站建设seo
  • 小黑享受思考心流: 132. 分割回文串 II
  • java求职学习day38
  • Golang—channel
  • 推三返一链动模式图解
  • 【人工智能与机器人研究】一种库坝系统水下成像探查有缆机器人系统设计模式
  • Qt---setAttribute设置控件或窗口的内部属性
  • 储能的“胜负手”:容量、策略与经济性如何平衡?
  • 蓝桥杯出局,少儿编程的价值祛魅时刻?
  • TensorFlow2 Python深度学习 - 使用TensorBoard可视化数据
  • wordpress忘记了密码忘记网站优化文章
  • 怎么看网站用哪个系统做的泰安集团网站建设方案
  • 在 openEuler 上为 LLVM/ASan 增强 wchar_t 字符串函数支持的开源贡献实践
  • git的命令
  • php mysql 网站源码北京网络营销培训