news 2026/3/17 4:26:12

Hot 146 LRU Cache 实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hot 146 LRU Cache 实现详解

一、问题背景

LRU(Least Recently Used)缓存是一种缓存淘汰策略:

  • 当缓存容量满时,删除最近最少使用的数据
  • 要求 get 和 put 操作的时间复杂度为 O(1)

二、数据结构选择

要实现 O(1) 操作,需要两种数据结构配合:

  1. HashMap:实现 O(1) 的查找
  • Map<Integer, Node>:key 到节点的映射

2.双向链表:实现 O(1) 的插入和删除,维护访问顺序

  • 头部:最近使用的节点
  • 尾部:最近最少使用的节点

三、核心设计:虚拟头尾节点

使用虚拟头尾节点简化边界处理:

head(虚拟)<-> Node1 <-> Node2 <-> Node3 <-> tail(虚拟)
↑最近使用 ↑最近最少使用

优势:

  • 统一处理:所有实际节点都有前驱和后继
  • 代码简洁:不需要特殊判断边界情况
  • 减少错误:避免空指针异常

四、完整代码实现

import java.util.*; class LRUCache { // 双向链表节点 class Node { int key; int value; Node prev; Node next; Node() {} Node(int key, int value) { this.key = key; this.value = value; } } private int capacity; private Map<Integer, Node> cache; private Node head; // 虚拟头节点 private Node tail; // 虚拟尾节点 public LRUCache(int capacity) { this.capacity = capacity; this.cache = new HashMap<>(); this.head = new Node(); this.tail = new Node(); head.next = tail; tail.prev = head; } public int get(int key) { Node node = cache.get(key); if (node == null) { return -1; } moveToHead(node); // 移到头部,标记为最近使用 return node.value; } public void put(int key, int value) { Node node = cache.get(key); if (node == null) { // key不存在,插入新节点 Node newNode = new Node(key, value); if (cache.size() >= capacity) { // 容量已满,删除最近最少使用的节点 Node lastNode = removeTail(); cache.remove(lastNode.key); } addToHead(newNode); cache.put(key, newNode); } else { // key已存在,更新值并移到头部 node.value = value; moveToHead(node); } } // 将节点添加到头部 private void addToHead(Node node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } // 移除节点 private void removeNode(Node node) { node.prev.next = node.next; node.next.prev = node.prev; } // 将节点移到头部 private void moveToHead(Node node) { removeNode(node); addToHead(node); } // 移除尾部节点(最近最少使用) private Node removeTail() { Node lastNode = tail.prev; removeNode(lastNode); return lastNode; } }

五、关键方法详解

1. get(int key) - 获取值
public int get(int key) { Node node = cache.get(key); if (node == null) { return -1; } moveToHead(node); // 关键:移到头部标记为最近使用 return node.value; }

流程:

  1. 从 HashMap 查找:O(1)
  2. 不存在返回 -1
  3. 存在则移到头部(标记为最近使用)
  4. 返回 value
2. put(int key, int value) - 插入/更新
public void put(int key, int value) { Node node = cache.get(key); if (node == null) { // 插入新节点 Node newNode = new Node(key, value); if (cache.size() >= capacity) { Node lastNode = removeTail(); cache.remove(lastNode.key); // 从HashMap中删除 } addToHead(newNode); cache.put(key, newNode); } else { // 更新已存在的节点 node.value = value; moveToHead(node); } }

两种情况:

  • key 不存在:创建新节点,容量满时删除尾部节点
  • key 已存在:更新 value 并移到头部
3. addToHead(Node node) - 添加到头部
private void addToHead(Node node){ node.prev=head; node.next=head.next; head.next.prev=node; head.next=node; }

操作步骤:

原来:head <-> node1 <-> tail
插入 node:head <-> node <-> node1 <-> tail

1. node.prev = head (node的前驱指向head)
2. node.next = head.next (node的后继指向原来的第一个节点)
3. head.next.prev = node (原来第一个节点的前驱指向node)
4. head.next = node (head的后继指向node)

4. removeNode(Node node) - 移除节点
private void removeNode(Node node){ node.prev.next=node.next; node.next.prev=node.prev; }

操作步骤:

原来:prev <-> node <-> next 删除后:prev <-> next 1. node.prev.next = node.next (前驱的后继指向node的后继) 2. node.next.prev = node.prev (后继的前驱指向node的前驱)

六、执行流程示例

假设 capacity = 2,执行以下操作:

1. put(1, 1)
cache: {1 -> Node(1,1)}
链表: head <-> Node(1,1) <-> tail

2. put(2, 2)
cache: {1 -> Node(1,1), 2 -> Node(2,2)}
链表: head <-> Node(2,2) <-> Node(1,1) <-> tail

3. get(1)
找到 Node(1,1),移到头部
链表: head <-> Node(1,1) <-> Node(2,2) <-> tail
返回: 1

4. put(3, 3)
容量已满,删除尾部 Node(2,2)
插入新节点 Node(3,3) 到头部
cache: {1 -> Node(1,1), 3 -> Node(3,3)}
链表: head <-> Node(3,3) <-> Node(1,1) <-> tail

七、总结

LRU Cache 实现要点:

  1. 数据结构:HashMap + 双向链表
  2. 虚拟节点:简化边界处理
  3. 访问顺序:头部 = 最近使用,尾部 = 最近最少使用
  4. 时间复杂度:所有操作 O(1)
  5. 关键操作:get/put 时都要将节点移到头部

这是一个经典的数据结构组合应用,在面试中经常出现。掌握这个实现,对理解缓存机制和数据结构设计很有帮助。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/16 5:27:01

新手必看:Keil5汉化包基础配置步骤

Keil5汉化包实战指南&#xff1a;新手避坑与高效配置全解析你是不是刚打开Keil μVision 5时&#xff0c;面对满屏的“Project”、“Target”、“Debug”一头雾水&#xff1f;是不是在查资料时发现中文教程里的菜单叫“工程”&#xff0c;而你的软件却是英文&#xff0c;来回对…

作者头像 李华
网站建设 2026/3/11 16:29:25

Multisim汉化完整示例:基于Win11系统的汉化实践记录

Multisim汉化实战全记录&#xff1a;从Win11权限陷阱到中文界面无缝切换你有没有过这样的经历&#xff1f;打开Multisim准备做电路仿真&#xff0c;结果满屏英文菜单、对话框和属性标签扑面而来——“Resistor”、“Capacitor”还能猜&#xff0c;“Hierarchical Block”、“MC…

作者头像 李华
网站建设 2026/3/17 0:14:34

ioctl接口设计要点:核心要点一文说清

深入理解 ioctl 接口设计&#xff1a;从原理到最佳实践在 Linux 内核驱动开发中&#xff0c;ioctl是连接用户空间与设备硬件的“控制开关”。它不像read和write那样处理数据流&#xff0c;而是专门用于执行那些无法用标准 I/O 表达的动作型操作——比如配置工作模式、触发一次采…

作者头像 李华
网站建设 2026/3/16 16:59:49

HBuilderX下载支持的开发语言全面讲解

一次下载&#xff0c;多端开发&#xff1a;HBuilderX 如何用一套工具打通全栈语言链&#xff1f;你有没有过这样的经历&#xff1f;写前端用 VS Code&#xff0c;调试小程序切到微信开发者工具&#xff0c;打包 App 又得打开 Android Studio&#xff0c;后端接口还得另开一个 W…

作者头像 李华
网站建设 2026/3/13 14:01:11

HuggingFace每周精选:最受欢迎的PyTorch模型榜单

HuggingFace每周精选&#xff1a;最受欢迎的PyTorch模型榜单 在深度学习领域&#xff0c;时间就是生产力。你有没有经历过这样的场景&#xff1a;好不容易找到了一个HuggingFace上评分极高的新模型&#xff0c;兴冲冲地准备复现论文结果&#xff0c;却卡在了环境配置这一步——…

作者头像 李华
网站建设 2026/3/13 6:14:43

论文分享|递归深度模型:情感树库上的语义组合性突破

引言&#xff1a;从词袋模型到结构感知的语义理解 情感分析&#xff0c;作为自然语言处理中最具实用价值的分支之一&#xff0c;长久以来面临着“语义组合性”这一核心挑战。传统的主流方法&#xff0c;如朴素贝叶斯或支持向量机&#xff0c;严重依赖于“词袋”假设。它们统计…

作者头像 李华