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

函数 get 和 put 必须以 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

思路:这道题的难点在于记录最近最少使用,使用map可以满足get的O(1),但是无法记录最近最少使用的数据;如果使用数组,删除/增加的时间复杂度则是O(n),也不满足。

使用哈希表 + 双向链表可以满足删除/增加的时间复杂度为O(1)。

这个图太形象了。

(1)双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的

(2)哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

(3)对于 get 操作,首先判断 key 是否存在:

        (a)如果 key 不存在,则返回 −1;

        (b)如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。

(3)对于 put 操作,首先判断 key 是否存在:

        (a)如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;

        (b)如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。

思路很清晰

class LRUCache {
public:LRUCache(int capacity) {}int get(int key) {}void put(int key, int value) {}
};/*** 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)定义双链表

struct DLinkedNode {int key, value;             // k-vDLinkedNode* prev;          // 前向指针DLinkedNode* next;          // 后向指针// 两个构造函数DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

(2)在LRUCache类中添加成员属性:哈希表+双向链表

class LRUCache {
public:// 新加的unordered_map<int, DLinkedNode*> cache;DLinkedNode* head;            // 伪头节点,不存数据DLinkedNode* tail;            // 伪尾节点,不存数据int size;                     // 当前存储的数量,当size==capacity时,要移出数据了int capacity;                 // 容量// 实现构造函数LRUCache(int _capacity): capacity(_capacity), size(0) {// 使用伪头节点和伪尾节点,不存数据head = new DLinkedNode();tail = new DLinkedNode();// 开始时一个数据都没有head->next = tail;tail->prev = head;}int get(int key) {}void put(int key, int value) {}
};

(3)实现双向链表中的【在头部添加数据】、【任意位置删除数据】、【数据移动到头部】、【从尾部删除数据】

在头部添加数据

    // 在头部添加数据void addToHead(DLinkedNode* node) {node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;}

任意位置删除数据

    // 任意位置删除数据void removeNode(DLinkedNode* node) {node->prev->next = node->next;node->next->prev = node->prev;}

数据移动到头部

    // 移动数据到头部void moveToHead(DLinkedNode* node) {removeNode(node);addToHead(node);}

从尾部删除数据

    // 从尾部删除数据DLinkedNode* reoveTail() {DLinkedNode* node = tail->prev;removeNode(node);return node;}

(4)实现get函数

如果不存在直接返回-1,存在的话,先通过哈希表定位,再移动到头部

    int get(int key) {// 不存在if (cache.count(key) == 0) {return -1;}// 通过哈希找到,移动到头部DLinkedNode* node = cache[key];moveToHead(node);return node->value;}

(5)实现put函数

如果key不存在,则创建一个节点,注意size==capacity的情况,此时删除队尾数据

靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的

如果存在,修改value,再将该节点移动到队头

 void put(int key, int value) {// 不存在if (cache.count(key) == 0) {DLinkedNode* node = new DLinkedNode(key, value);cache[key] = node;          // 添加到哈希表中addToHead(node);            // 移动到队头size++;if (size > capacity) {DLinkedNode* removeNode = reoveTail();  // 删除尾部数据cache.erase(removeNode->key);           // 删除哈希中的数据delete removeNode;size--; }} else {DLinkedNode* node = cache[key];node->value = value;moveToHead(node);               // 移到队头}}

全部代码实现

struct DLinkedNode {int key, value;             // k-vDLinkedNode* prev;          // 前向指针DLinkedNode* next;          // 后向指针// 两个构造函数DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};class LRUCache {
public:unordered_map<int, DLinkedNode*> cache;DLinkedNode* head;DLinkedNode* tail;int size;int capacity;LRUCache(int _capacity): capacity(_capacity), size(0) {// 使用伪头节点和伪伪节点,不存数据head = new DLinkedNode();tail = new DLinkedNode();// 开始时一个数据都没有head->next = tail;tail->prev = head;}int get(int key) {// 不存在if (cache.count(key) == 0) {return -1;}// 通过哈希找到,移动到头部DLinkedNode* node = cache[key];moveToHead(node);return node->value;}void put(int key, int value) {// 不存在if (cache.count(key) == 0) {DLinkedNode* node = new DLinkedNode(key, value);cache[key] = node;          // 添加到哈希表中addToHead(node);            // 移动到队头size++;if (size > capacity) {DLinkedNode* removeNode = reoveTail();  // 删除尾部数据cache.erase(removeNode->key);           // 删除哈希中的数据delete removeNode;size--; }} else {DLinkedNode* node = cache[key];node->value = value;moveToHead(node);               // 移到队头}}// 在头部添加数据void addToHead(DLinkedNode* node) {node->prev = head;node->next = head->next;head->next->prev = node;head->next = node;}// 任意位置删除数据void removeNode(DLinkedNode* node) {node->prev->next = node->next;node->next->prev = node->prev;}// 移动数据到头部void moveToHead(DLinkedNode* node) {removeNode(node);addToHead(node);}// 从尾部删除数据DLinkedNode* reoveTail() {DLinkedNode* node = tail->prev;removeNode(node);return 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);*/

参考:【字节一面】 LRU Cache 实现剖析_哔哩哔哩_bilibili

链接:. - 力扣(LeetCode)

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

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

相关文章

【InternLM 实战营笔记】浦语·灵笔的图文理解及创作部署、 Lagent 工具调用 Demo

浦语灵笔的图文理解及创作部署 浦语灵笔是基于书生浦语大语言模型研发的视觉-语言大模型&#xff0c;提供出色的图文理解和创作能力&#xff0c;结合了视觉和语言的先进技术&#xff0c;能够实现图像到文本、文本到图像的双向转换。使用浦语灵笔大模型可以轻松的创作一篇图文推…

进程间的通信 -- 共享内存

一 共享内存的概念 1. 1 共享内存的原理 之前我们学过管道通信&#xff0c;分为匿名管道和命名管道&#xff0c;匿名管道通过父子进程的属性继承原理来完成父子进程看到同一份资源的目的&#xff0c;而命名管道则是通过路径与文件名来唯一标识管道文件&#xff0c;来让不同的进…

typescript 的常用方式

文章目录 前言一、绑定props 默认值的方式&#xff1a;withDefaults1.vue2 的props设置默认值2.vue3 的props设置默认值(1) 不设置默认值的写法(2) 设置默认值的写法&#xff08;分离模式&#xff09;(3) 设置默认值的写法&#xff08;组合模式&#xff09; 二、定义一个二维数…

Matlab在同一张图中如何加入多个图例

根据代码最终画出的图片如下&#xff1a; 其实原理很简单&#xff0c;就是在一张figure中画多个坐标轴&#xff0c;每个坐标轴都有对应的图例&#xff0c;之后再将多余坐标轴隐藏&#xff0c;只保留一个即可。 代码如下&#xff1a; clear all; close all;dd_linewidth 1;a …

maven archetype 项目原型

拓展阅读 maven 包管理平台-01-maven 入门介绍 Maven、Gradle、Ant、Ivy、Bazel 和 SBT 的详细对比表格 maven 包管理平台-02-windows 安装配置 mac 安装配置 maven 包管理平台-03-maven project maven 项目的创建入门 maven 包管理平台-04-maven archetype 项目原型 ma…

Spring学习笔记(六)利用Spring的jdbc实现学生管理系统的用户登录功能

一、案例分析 本案例要求学生在控制台输入用户名密码&#xff0c;如果用户账号密码正确则显示用户所属班级&#xff0c;如果登录失败则显示登录失败。 &#xff08;1&#xff09;为了存储学生信息&#xff0c;需要创建一个数据库。 &#xff08;2&#xff09;为了程序连接数…

php源码 单色bmp图片取模工具 按任意方式取模 生成字节数组 自由编辑点阵

http://2.wjsou.com/BMP/index.html 想试试chatGPT4生成&#xff0c;还是要手工改 php 写一个网页界面上可以选择一张bmp图片&#xff0c;界面上就显示这张bmp图片&#xff0c; 点生成取模按钮&#xff0c;在图片下方会显示这张bmp图片的取模数据。 取模规则是按界面设置的&a…

Linux 的交换空间(swap)是什么?有什么用?

目录 swap是什么&#xff1f;swap有什么用&#xff1f;swap使用典型场景如何查看你的系统是否用到交换空间呢&#xff1f;查看系统中swap in/out的情况 swap是什么&#xff1f; swap就是磁盘上的一块区域。它和Windows系统中的交换文件作用类似&#xff0c;但是它是一段连续的…

03、MongoDB -- MongoDB 权限的设计

目录 MongoDB 权限的设计演示前准备&#xff1a;启动 mongodb 服务器 和 客户端 &#xff1a;1、启动单机模式的 mongodb 服务器2、启动 mongodb 的客户端 MongoDB 权限的设计1、MongoDB 的每个数据库都可以保存用户&#xff0c;不止admin数据库可以保存用户。2、保存用户的数据…

Linux 学习笔记(8)

八、 启动引导 1 、 Linux 的启动流程 1) BIOS 自检 2) 启动 GRUB/LILO 3) 运行 Linux kernel 并检测硬件 4) 挂载根文件系统 5) 运行 Linux 系统的第一个进程 init( 其 PID 永远为 1 &#xff0c;是所有其它进程的父进程 ) 6) init 读取系统引导配置文件…

GD25Q32驱动

GD25Q32是一款基于SPI的Flash芯片&#xff0c;容量为32/84M bytes。它的引脚如下&#xff1a; 该芯片支持多种SPI操作方式&#xff0c;包括&#xff1a;Standard SPI(标准SPI)、Dual SPI(双线 SPI)和Quad SPI(四线 SPI) 。有关SPI的介绍可以参考&#xff1a; SPI通信原理-CSDN…

flutter 文字一行显示,超出换行

因为app有多语言&#xff0c;中文和其他语言长度不一致&#xff0c;可能导致英文会很长。 中文样式 英文样式 代码 Row(mainAxisAlignment: MainAxisAlignment.end,crossAxisAlignment: CrossAxisAlignment.end,children: [Visibility(visible: controller.info.fee ! null,ch…

探寻2024年国内热门低代码平台排行!| 功能特点一览

低代码开发是一项革命性的技术&#xff0c;主要目的是尽量避免程序研发的复杂性&#xff0c;让外行开发者也能加入到应用程序的搭建中。低代码平台的核心概念和构成部分通常包括用户界面和拖拽设计、预构件和模块、自动化工作内容与数据库集成和扩展应用&#xff0c;应用低代码…

U盘弹出提示“该设备正在使用中”:原因与解决方案

在日常使用U盘时&#xff0c;我们可能会遇到一个问题&#xff1a;当尝试安全弹出U盘时&#xff0c;系统提示“该设备正在使用中”。这种情况可能会让用户感到困惑&#xff0c;担心数据是否安全或是否会导致U盘损坏。本文旨在探讨这一现象背后的原因&#xff0c;并提供相应的解决…

【前端素材】推荐优质后台管理系统网页Stisla平台模板(附源码)

一、需求分析 1、系统定义 后台管理系统是一种用于管理和控制网站、应用程序或系统的管理界面。它通常被设计用来让网站或应用程序的管理员或运营人员管理内容、用户、数据以及其他相关功能。后台管理系统是一种用于管理网站、应用程序或系统的工具&#xff0c;通常由管理员使…

铅冶炼作业VR虚拟现实互动培训平台降低实操风险

在钢铁工业中&#xff0c;焦炉作业是一个关键的环节&#xff0c;也是一项技术要求高、操作复杂的任务。传统焦炉作业的培训通常需要在实际的焦炉上进行&#xff0c;这不仅对学员的身体素质和心理素质提出了较高的要求&#xff0c;而且也存在一定的安全风险。基于VR虚拟现实制作…

React富文本编辑器开发(三)

现在我们的编辑器显示的内容很单一&#xff0c;这自然不是我们的目标&#xff0c;让呈现的内容多元化是我们的追求。这就需要让编辑器能够接收多元素的定义。从初始数据的定义我们可以推断数据的格式远不止一种&#xff0c;那么其它类型的数据如何定义及呈现的呢&#xff0c;我…

手把手教你免费用Flashduty做消息通知

为什么需要消息通知&#xff1f; 如果有重要的情况发生&#xff0c;希望能通过各种媒介通知我们。可以举几个例子&#xff1a; 家里燃气费没有了&#xff0c;希望能有短信或者app通知api频繁500报错&#xff0c;希望及时感知&#xff0c;及时修复公司网站是https自签名证书&a…

白话大模型① :AI分析能做什么?在实际落地中会碰到什么问题?

白话大模型系列共六篇文章&#xff0c;将通俗易懂的解读大模型相关的专业术语。本文为第一篇&#xff1a;AI分析能做什么&#xff1f;在实际落地中会碰到什么问题&#xff1f; 作者&#xff1a;星环科技 人工智能产品部 我们使用一个简单的应用实例来解析人工智能分析都在做什…

若依框架使用mars3d的环境配置,地球构建

因项目需要&#xff0c;原本使用过的cesium依赖&#xff0c;现在想使用火星科技mars3d的一些功能&#xff0c;所以需要引入mars3d依赖&#xff0c;整个过程非常的坎坷&#xff0c;以至于我都不知道到底是哪些部分是标准的。。。先把我认为对的记录一下&#xff1a; 1.vue.conf…