图解LeetCode——146. LRU 缓存

news2024/12/26 14:19:48

一、题目

请你设计并实现一个满足  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) 的平均时间复杂度运行。

二、示例

2.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 <= 10^5
  • 最多调用 2 * 10^5 次 get 和 put

三、解题思路

根据题目描述,我们需要构造一个LRU缓存,那么我们需要解决两个问题:

问题1】实现对keyvalue的数据进行缓存。
问题2】实现LRU的能力,即:最近最少使用。

那么针对第一个问题,我们可以采用哈希表或者数组的方式进行数据存储,因为本题的提示部分已经指出key值是在[0, 10000]区间内的,并不存在负数,所以为了提升执行速度,我们选择数组作为底层的存储结构。其中,需要注意的是,存储的value是下面要介绍的双向链表中的Node节点

那么针对第二个问题,我们可以采用双向链表的方式进行支持,那么为什么是双向链表呢?因为当调用lRUCache.get()lRUCache.set()方法对某个Node进行操作的时候,我们需要将这个Node放到链表的尾部,这样,就需要操作该Node节点的前置节点后置节点,为了便于这种操作,所以我们采用双向链表。执行步骤如下所示:

步骤1】断开PreNodeNode的链接关系;
步骤2】断开NextNodeNode的链接关系;
步骤3】链接PreNodeNextNode
步骤4】将Node放到链表尾部;

那么这里还有一个小细节,就是如果待移动的节点在头节点,那么我们还需要进行特殊的判断(因为头节点没有前置节点PreNode),而同样的,如果待删除的节点是尾节点,那么我们也需要进行特殊的判断(因为尾节点没有后置节点NextNode)。为了统一处理逻辑,我们可以通过创建虚拟的头尾节点来解决这个问题,即:

虚拟头节点】Node head = new Node(-1, -1);
虚拟尾节点】Node tail = new Node(-2, -1);
初始情况下head.next = tail; tail.pre = head;

由于我们可以知道LRU链表容量的,所以当超出这个容量的时候,就将整个链表中,第一个节点删除即可(不包含虚拟收尾节点),并将哈希表/数组中相应key对应的value置为null;以上就是这道题的解题思路,为了便于大家理解,我们通过创建2节点容量的LRU,具体看一下具体的处理过程,请见下图所示:

四、代码实现

class LRUCache {
    Node[] hashTable; // 哈希表
    int capacity = 0, count = 0;
    Node head, tail;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        hashTable = new Node[10001];
        head = new Node(-1, -1); // 虚拟头节点
        tail = new Node(-2, -1); // 虚拟尾节点
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        Node node;
        if ((node = hashTable[key]) == null) return -1; // 如果不存在,直接返回-1
        moveNode(node); // 将node节点移动到末尾(位置是tail节点的pre节点)
        return node.value;
    }

    public void put(int key, int value) {
        Node node;
        if ((node = hashTable[key]) != null) { // 已存在节点,直接修改value值
            node.value = value;
        } else { // 不存在节点,则新建Node
            if (count < capacity) { // 没有到达容量上限
                count++;
            } else { // 超出容量上限,则需要删除“最近最少使用”的节点
                hashTable[head.next.key] = null;
                delNode(head.next);
            }
            node = new Node(key, value);
            hashTable[key] = node;
        }
        moveNode(node); // 将node节点移动到末尾
    }

    // 删除节点操作
    public void delNode(Node node) {
        if (node.pre == null || node.next == null) return;
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    // 将节点移动到“末尾”操作(位置是tail节点的pre节点)
    public void moveNode(Node node) {
        if (tail.pre == node) return; // 如果已经是“末尾”节点,则不操作
        delNode(node); // 删除该节点的前后关联,下面会进行重新关联操作
        tail.pre.next = node;
        node.pre = tail.pre;
        node.next = tail;
        tail.pre = node;
    }

    // 双向链表结构
    class Node {
        public int key, value;
        public Node pre, next;
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
}

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。

更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/593215.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么才是自动化测试框架?最流行的自动化测试框架整理,你的进阶之路...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

Smartbi助力航天百亿级遥测数据实现秒级查询

“Smartbi全程参与了火星探测任务、中国载人空间站建设任务&#xff0c;为航天任务参战单位提供专业、易用、高性能的实时数据查询分析监控平台&#xff0c;实现航天器飞行状态监测和预警&#xff0c;让咱们的科研人员专注聚焦科研工作&#xff0c;保障航天任务顺利进行。Smart…

Benewake(北醒) 快速实现 TF02-i-RS485 与电脑通信操作说明

目录 一、前言二、工具准备1. USB-RS485 转接器2. TF02-i-RS4853. 兆信直流电源4.连接线、绝缘胶带、螺丝刀5. PC&#xff1a;Windows 系统6. 串口助手软件 三、连接方式1. USB-RS485 转接板接口说明2. TF02-i-RS485 引脚定义3. 连接图 四、TF02-i-RS485 与电脑通信操作说明1. …

js中+new Date()

在学习js过程中遇到了这样的写法&#xff1a; var nownew Date(); 在这段代码中有一个号&#xff0c;上网查阅得知在前面加一个号是涉及到了隐式转换&#xff0c;也就是触发对象执行valueof进行求值。 valueof可以用来得到现在时间距离1970.1.1总的毫秒数&#xff1a; var …

Android12之源码手动生成aidl对应java/cpp/ndk/rust服务(一百五十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【自动化项目实战】博客系统

目录 1.博客自动化测试用例 2.准备工作 3.将手工测试用例转化为自动化测试用例 3.1 初始化动作 3.2 登录 3.3 博客列表博客数量 3.4 查看全文 3.5 写博客&发表博客 3.6 删除 3.7 注销 4.总代码 &#x1f308;这节文章我们讲解一个实战项目——博客系统。首先我…

在前公司年薪38W,经人内推腾讯居然被拒了···

末流院校&#xff0c;带17人研发团队&#xff0c;到手38w股票20w&#xff0c;过硬的技术让我觉得可以出去“闯闯”;内推到某大厂&#xff0c;电话里聊得挺好&#xff0c;结果第二天说不给安排面试了…… 被拒绝很正常&#xff0c;想必应该是能力不足&#xff0c;不能满足公司的…

太难了,00后求求你们别这么卷了....

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&#x…

网络传输(传输介质、通信方式、交换方式)

目录 一、传输介质1.双绞线2.网线安装3.光纤4.无线信道 二、通信方式、交换方式1.通信方式2.同步方式3.交换方式 一、传输介质 1.双绞线 双绞线&#xff1a;将多根铜线按规则缠绕在一起&#xff0c;能够减少干扰&#xff1b;分为无屏蔽双绞线UTP和屏蔽双绞线STP&#xff0c;都…

PCIE学习

目录 一、PCIE结构1、层次结构2、数据包TLPDLLP PCIE寄存器配置1、基址寄存器的作用2、基址寄存器的位置 三、PCIE读取数据 一、PCIE结构 1、层次结构 绝大多数的总线或者接口&#xff0c;都是采用分层实现的。PCIe也不例外&#xff0c;它的层次结构如下&#xff1a; PCIe定…

MAYA绳子和铁链动画(3个例子)

一两条边中间加定位器 // Copyright (C) 2000-2001 Michael Bazhutkin - Copyright (C) 2000 studio Klassika // www.geocites.com/bazhutkin // bazhutkinmail.ru // // Rivet (button) 1.0 // Script File // MODIFY THIS AT YOUR OWN RISK // // Creation Date: Apri…

【Unittest】自动化测试框架核心要素

1、什么是Unittest框架&#xff1f; python自带一种单元测试框架 2、为什么使用UnitTest框架&#xff1f; >批量执行用例 >提供丰富的断言知识 >可以生成报告 3、核心要素&#xff1a; 1). TestCase&#xff08;测试用例&#xff09; 2). TestSuite(测试套件)…

系统分析师:七、软件工程(含系统规划)

一、软件生命周期 软件生命周期分为5个&#xff1a;获取过程、供应过程、开发过程、运行过程、维护过程&#xff0c;具体如下&#xff1a; 二、软件开发方法 2.1 形式化方法 该方法的思想是利用形式化语言&#xff0c;严格定义需求&#xff0c;并用数据推演的方法证明需求的性…

隐藏在Microsoft Designer背后的新科技,让人人都是设计师

编者按&#xff1a;在视觉图像设计中&#xff0c;用户的需求与最终的设计成品往往是“想象很美好&#xff0c;现实很骨感”。这通常是因为用户在与设计师沟通时&#xff0c;双方理解不一致&#xff0c;导致最终设计结果不尽如人意。但是&#xff0c;如果能够“自给自足”&#…

COMSOL晶体取向多晶材料Voronoi泰森多边形力学模拟

多晶材料几何模型模型构建采用的CAD Voronoi V2.3版本&#xff0c;可分图层对晶格进行绘制&#xff0c;分别导入有限元软件后实现三种晶体取向的差异性。 将构建好的Voronoi多晶体几何模型文件导入到COMSOL内&#xff0c;构建好晶体结构模型后&#xff0c;进行材料赋值操作&am…

Oracle常用傻瓜问题100问

大家在应用ORACLE的时候可能会遇到很多看起来不难的问题, 特别对新手来说, 今天我简单把它总结一下, 发布给大家, 希望对大家有帮助! 和大家一起探讨, 共同进步! 对ORACLE高手来说是不用看的. 1. Oracle安装完成后的初始口令? internal/oracle sys/change_on_install system/m…

Spring6《学习笔记(22版尚硅谷)》

Spring6 1、概述 1.1、Spring是什么&#xff1f; Spring 是一款主流的 Java EE 轻量级开源框架 &#xff0c;Spring 由“Spring 之父”Rod Johnson 提出并创立&#xff0c;其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单…

io之socket编程

写在前面 本文通过socket编程来实现一个简单的HttpServer。 1&#xff1a;单线程版本 我们使用单线程来实现一个HttpServer&#xff0c;如下&#xff1a; package dongshi.daddy.io.httpserver;import java.io.PrintWriter; import java.net.ServerSocket; import java.net.…

何为儒家的四书五经?

中国古代的四书五经是儒家经典之一&#xff0c;是中国古代最为重要的经典之一。它们包括了四书&#xff1a;《大学》、《中庸》、《论语》、《孟子》以及五经&#xff1a;《诗经》、《尚书》、《礼记》、《周易》、《春秋》&#xff0c;被誉为“经国之宝”、“德育之本”。 四书…

java服务接入SkyWalking时生成TraceId信息(基于logback)

java服务生成TraceId 一、背景二、配置2.1 pom文件引入依赖2.2 logback-spring.xml配置 三、启动项目 一、背景 springboot服务接入SkyWalking时&#xff0c;想要在控制台输出TraceId信息&#xff0c;如下图的效果&#xff1a; 二、配置 参考文章&#xff1a; https://juej…