news 2026/5/30 21:09:08

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/5/28 15:18:03

Altium Designer基础篇:创建原理图符号的实战案例

从零开始掌握Altium Designer&#xff1a;手把手教你创建一个专业的LM358原理图符号在硬件设计的世界里&#xff0c;每一个精密的电路板都始于一张清晰、准确的原理图。而原理图的灵魂&#xff0c;正是那些看似简单却至关重要的元件符号。你有没有遇到过这样的情况&#xff1f;…

作者头像 李华
网站建设 2026/5/28 21:13:05

PyTorch-CUDA-v2.9镜像安装全攻略:轻松配置GPU加速深度学习环境

PyTorch-CUDA-v2.9镜像安装全攻略&#xff1a;轻松配置GPU加速深度学习环境 在深度学习项目中&#xff0c;最让人头疼的往往不是模型设计&#xff0c;而是环境搭建——尤其是当你面对“CUDA not available”、“driver version mismatch”这类报错时&#xff0c;那种无力感几乎…

作者头像 李华
网站建设 2026/5/28 12:23:59

nohup运行PyTorch脚本防止终端断开中断训练

nohup运行PyTorch脚本防止终端断开中断训练 在深度学习项目中&#xff0c;最让人沮丧的场景之一莫过于&#xff1a;你启动了一个耗时数小时甚至数天的模型训练任务&#xff0c;结果因为本地电脑休眠、网络波动或不小心关闭了终端&#xff0c;导致整个进程被中断——所有进度付诸…

作者头像 李华
网站建设 2026/5/29 13:14:42

模型水印技术追踪非法分发的PyTorch权重文件

模型水印技术追踪非法分发的PyTorch权重文件 在AI模型逐渐成为企业核心资产的今天&#xff0c;一个训练有素的深度学习模型可能耗费数月时间和巨额算力成本。然而&#xff0c;一旦其权重文件被泄露或非法复制&#xff0c;侵权者几乎可以在零成本的情况下复现相同能力——这就像…

作者头像 李华
网站建设 2026/5/27 17:03:00

多相DC-DC变换器中电感均流问题深度剖析

多相DC-DC变换器中的电感均流&#xff1a;从原理到实战的系统性突破在高性能计算、AI训练芯片、5G基站和电动汽车主控板这些高功率密度系统中&#xff0c;电源不再是“配角”。一个设计不佳的供电模块&#xff0c;可能直接拖垮整颗价值百万的GPU。而在这些系统的“心脏”——多…

作者头像 李华
网站建设 2026/5/28 14:21:22

KV Cache优化策略减少重复计算提升效率

KV Cache优化策略减少重复计算提升效率 在大语言模型&#xff08;LLM&#xff09;日益普及的今天&#xff0c;用户对生成速度和响应延迟的要求越来越高。无论是聊天机器人、代码补全&#xff0c;还是长文本生成任务&#xff0c;逐 token 自回归输出的模式虽然逻辑清晰&#xff…

作者头像 李华