news 2026/4/23 13:21:14

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/4/23 8:23:28

手把手教程:在ARM64实例上搭建Kubernetes集群

在 ARM64 服务器上从零搭建 Kubernetes 集群&#xff1a;一次真实的实战记录最近&#xff0c;我在 AWS 上启动了一台 T4g 实例&#xff08;基于 Graviton2 的 arm64 架构&#xff09;&#xff0c;想试试在非 x86 平台上部署一套完整的 Kubernetes 集群。起初我以为只是换个架构…

作者头像 李华
网站建设 2026/4/23 8:22:16

参与PyTorch开源项目提升个人技术影响力

参与 PyTorch 开源项目提升个人技术影响力 在人工智能研发日益标准化的今天&#xff0c;一个刚入门的研究生和一家顶级科技公司的工程师可能使用完全相同的工具链&#xff1a;PyTorch 搭配 CUDA 加速&#xff0c;在容器化环境中完成从实验到部署的全流程。这种一致性背后&#…

作者头像 李华
网站建设 2026/4/23 9:57:00

从零实现同步整流buck电路图及其原理分析

从零构建同步整流Buck电路&#xff1a;不只是看懂图&#xff0c;更要搞懂它为何高效你有没有遇到过这样的情况&#xff1f;设计一个电源模块时&#xff0c;明明选了“够用”的电感和二极管&#xff0c;结果负载一加大&#xff0c;芯片烫得像火炉&#xff0c;效率掉得比自由落体…

作者头像 李华
网站建设 2026/4/21 3:26:02

利用电路仿真软件分析频率响应的系统学习

深入掌握频率响应仿真&#xff1a;从原理到实战的完整指南你有没有遇到过这样的情况&#xff1f;电路在纸上设计得完美无缺&#xff0c;一上电却自激振荡、输出失真&#xff0c;甚至完全无法工作。而当你回头用示波器测量时&#xff0c;才发现问题出在某个“看不见”的频率点上…

作者头像 李华
网站建设 2026/4/22 14:03:07

SpringSecurity、Shiro 和 Sa-Token,选哪个更好?

前言 今天我们来聊聊一个让很多Java开发者纠结的技术选型问题&#xff1a;Spring Security、Apache Shiro和Sa-Token&#xff0c;这3个主流安全框架到底该选哪个&#xff1f; 有些小伙伴在工作中可能遇到过这样的场景&#xff1a;新项目启动会上&#xff0c;架构师坚持要用Sp…

作者头像 李华
网站建设 2026/4/23 9:59:55

Google Colab Pro解锁更高GPU算力运行大模型

Google Colab Pro解锁更高GPU算力运行大模型 在深度学习的世界里&#xff0c;算力就是生产力。当你的本地笔记本还在为加载一个7B参数的LLaMA模型而内存告急时&#xff0c;有人已经用云端A100显卡完成了微调任务——这种差距的背后&#xff0c;不只是硬件配置的问题&#xff0c…

作者头像 李华