LeetCode 算法:LRU 缓存 c++

原题链接🔗:
难度:中等⭐️⭐️

题目

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key)如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 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

提示

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105 次 get 和 put

题解

  1. 解题思路

LeetCode 上的 LRU 缓存问题(题目编号 146)要求我们实现一个 LRU 缓存类,该类支持两种操作:get 和
put。以下是解决这个问题的一般思路:

  • 理解问题:
    • get(key): 从缓存中获取键对应的值(如果存在)。缓存中键的最近使用时间应该被更新为当前时间。 -
    • put(key, value): 向缓存中插入或更新键和值。如果键已存在,则更新其值并更新其使用时间。如果键不存在,并且缓存已满,则需要先移除最久未使用的键。
  • 设计数据结构: 为了实现 LRU 缓存,我们需要两个主要的数据结构:
    • 哈希表:存储键到值的映射,以及键到双向链表中节点的引用,以便 O(1) 时间复杂度的访问。
    • 双向链表:存储缓存项的顺序,最近使用的项在头部,最久未使用的项在尾部。
  • 算法实现:
    • 初始化:创建一个固定大小的哈希表和空的双向链表。
    • get(key): 检查键是否存在于哈希表中。 如果存在,移动该键关联的节点到双向链表的头部,表示最近使用。 返回键对应的值。 如果不存在,返回 -1。
    • put(key, value)
      • 如果键已存在于哈希表中,更新其值,并将节点移动到双向链表的头部。
      • 如果键不存在:
        • 如果缓存未满,创建新节点,添加到双向链表的头部,并在哈希表中添加键到节点的映射。
        • 如果缓存已满,从双向链表尾部移除最久未使用的节点,并从哈希表中删除对应的键。
        • 然后,创建新节点,添加到双向链表的头部,并更新哈希表。
  1. 复杂度:时间复杂度O(1),空间复杂度O(capacity)。
  2. c++ demo
#include <iostream>
#include <unordered_map>
#include <list>

class LRUCache {
private:
    int capacity;
    std::list<int> items; // 双向链表,存储键
    std::unordered_map<int, std::list<int>::iterator> cache; // 哈希表,存储键到链表迭代器的映射

    void moveToHead(int key) {
        items.splice(items.begin(), items, cache[key]); // 将节点移动到头部
    }

    void removeLeastRecent() {
        if (!items.empty()) {
            cache.erase(items.back()); // 从哈希表中删除
            items.pop_back(); // 从链表中删除
        }
    }

public:
    LRUCache(int capacity) : capacity(capacity) {}

    int get(int key) {
        if (cache.find(key) != cache.end()) {
            moveToHead(key);
            return items.front();
        }
        else {
            return -1; // 键不存在
        }
    }

    void put(int key, int value) {
        if (cache.find(key) != cache.end()) {
            items.erase(cache[key]); // 从链表中删除旧节点
            cache.erase(key);
        }
        if (items.size() >= capacity) {
            removeLeastRecent(); // 检查是否需要淘汰
        }
        items.push_front(key); // 添加新节点到头部
        cache[key] = items.begin(); // 更新哈希表
    }
};

int main() {
    LRUCache cache(2);

    cache.put(1, 1);
    std::cout << "Get 1: " << cache.get(1) << std::endl; // 返回 1
    cache.put(2, 2);
    std::cout << "Get 2: " << cache.get(2) << std::endl; // 返回 2
    cache.put(3, 3); // 淘汰键 1 使用 put
    std::cout << "Get 1: " << cache.get(1) << std::endl; // 返回 -1 (未找到)
    std::cout << "Get 3: " << cache.get(3) << std::endl; // 返回 3
    cache.put(4, 4); // 淘汰键 2 使用 put
    std::cout << "Get 2: " << cache.get(2) << std::endl; // 返回 -1 (未找到)
    cache.put(2, 6); // 重新添加键 2
    std::cout << "Get 2: " << cache.get(2) << std::endl; // 返回 6

    return 0;
}
  • 输出结果:

Get 1: 1
Get 2: 2
Get 1: -1
Get 3: 3
Get 2: -1
Get 2: 2

LRU 缓存

LRU(Least Recently Used)缓存是一种常用的缓存淘汰策略,它基于一个原则:如果数据最近被访问过,那么将来被访问的几率也更高。LRU 缓存通常用于操作系统、数据库、Web 服务器等场景,以减少内存或存储的使用,同时保持最常访问的数据可用。

LRU 缓存的实现

实现 LRU 缓存的一种常见方法是使用哈希表和双向链表。哈希表提供了 O(1) 的访问时间复杂度,而双向链表则允许我们以 O(1) 的时间复杂度进行数据的添加和删除操作。

数据结构
  1. 哈希表:存储键和对应节点的指针,以便快速访问。
  2. 双向链表:存储缓存中的项,最近使用的项在头部,最不常使用的项在尾部。
操作
  1. 访问缓存

    • 检查键是否存在于哈希表中。
    • 如果存在,将对应的节点移动到双向链表的头部。
    • 如果不存在,根据需要从链表尾部移除一个节点(如果缓存已满),然后将新节点添加到头部。
  2. 添加到缓存

    • 如果键已存在,更新其值,并将节点移动到链表头部。
    • 如果键不存在,创建新节点,添加到链表头部,并在哈希表中记录。
  3. 淘汰缓存

    • 当缓存达到容量限制时,从链表尾部移除最不常使用的项,并从哈希表中删除相应的键。

示例代码

以下是使用 C++ 实现的 LRU 缓存的简单示例:

#include <iostream>
#include <list>
#include <unordered_map>

class LRUCache {
private:
    int capacity;
    std::list<int> items; // 双向链表,存储键
    std::unordered_map<int, std::list<int>::iterator> cache; // 哈希表,存储键到链表迭代器的映射

    void moveToHead(int key) {
        items.splice(items.begin(), items, cache[key]); // 将节点移动到头部
    }

    void removeLeastRecent() {
        if (items.size() > capacity) {
            cache.erase(items.back()); // 从哈希表中删除
            items.pop_back(); // 从链表中删除
        }
    }

public:
    LRUCache(int capacity) : capacity(capacity) {}

    int get(int key) {
        if (cache.find(key) != cache.end()) {
            moveToHead(key);
            return items.front();
        } else {
            return -1; // 键不存在
        }
    }

    void put(int key, int value) {
        if (cache.find(key) != cache.end()) {
            items.erase(cache[key]); // 从链表中删除旧节点
        } else {
            removeLeastRecent(); // 检查是否需要淘汰
        }
        items.push_front(key); // 添加新节点到头部
        cache[key] = items.begin(); // 更新哈希表
    }
};

int main() {
    LRUCache cache(2);
    cache.put(1, 1);
    std::cout << cache.get(1) << std::endl; // 返回 1
    cache.put(2, 2);
    std::cout << cache.get(2) << std::endl; // 返回 2
    cache.put(3, 3); // 淘汰键 1
    std::cout << cache.get(1) << std::endl; // 返回 -1 (未找到)
    std::cout << cache.get(3) << std::endl; // 返回 3
    cache.put(4, 4); // 淘汰键 2
    std::cout << cache.get(2) << std::endl; // 返回 -1 (未找到)
    return 0;
}

这个示例展示了 LRU 缓存的基本操作,包括 getput 方法。get 方法用于访问缓存中的项,而 put 方法用于添加或更新缓存中的项。如果缓存达到容量限制,put 方法还会淘汰最不常使用的项。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java Web】PostMan业务接口测试工具

目录 一、PostMan概述 二、如何安装Postman 三、Postman的基本使用 一、PostMan概述 在生产环境中&#xff0c;一个项目在开发之前、前后端开发工程师通常需要商讨在前后端数据交互时需要采用什么样的规范格式&#xff0c;如&#xff1a;前端向后端发送请求的uri、请求和响…

vue2.0项目安装依赖 sass 报错

1、报错代码&#xff1a; 2、原因&#xff1a;项目有点老&#xff0c;vue2的版本&#xff0c;所以node-sass在npm安装的时候大概率的会安装出错&#xff0c;或下载时间过长&#xff0c;因此考虑用dart-sass来替换。 npm install node-sassnpm:dart-sass3、然后就可以成功运行了…

【PTA】7-1 网红点打卡攻略(C/C++)代码实现 反思

解题细节分析&#xff1a; 0.比较图的两种存储方法&#xff0c;通过邻接矩阵存储更便于查找给定两点之间的关系 1.注意理解清楚题义&#xff1a;“访问所有网红点”中所有不是指攻略中所有&#xff0c;而是存在的全部的网红点 代码见下&#xff1a;// 需要注明的是&#xff…

音频剪辑技巧:音频降噪在线怎么降噪?分享7种录音去除杂音方法

相信很多小伙伴们都有这种苦恼&#xff1a;在编辑音频时&#xff0c;你可能发现即使你使用了价格昂贵的隔音麦克风&#xff0c;在录音中仍然存在呼吸声和咳嗽声。因此&#xff0c;如果要传达清晰干净的声音以表达你的信息&#xff0c;你该如何从录音去除杂音呢&#xff1f;别心…

理清时间复杂度和空间复杂度

目录 复习时间 幂函数 指数函数 对数函数 ​编辑 时间复杂度 推导阶的原则 常见的时间复杂度举例 常数阶 O(1) 对数阶 O(logn) 平方阶 O(n^2) 图像表示 空间复杂度 常见的空间复杂度举例 常数阶 O(1) 线性阶 O(n) 平方阶 O(n^2) 一个算法的优劣主要从算法的执行时间和所需要占…

elementUI的衍生组件,avue的crud表格错位问题

问题描述&#xff1a; 每次从别的页面跳转回来就发现表格显示错位了 一通查 结果发现是有两层表格 解决办法&#xff1a; 根据开发者工具中看到的样式选择器&#xff0c;很粗暴的在全局样式文件中加一个&#xff1a; 效果&#xff1a;

CDP问卷的常见问题

CDP问卷的常见问题可以归纳如下&#xff1a; 哪些企业会收到CDP邀请&#xff1f; 企业会收到来自投资和/或采购机构的邀请&#xff0c;以填写CDP问卷并披露相应的环境管理信息。 未收到邀请的企业可否填报&#xff1f; 未收到邀请的企业可以选择自行填报。他们需发送申请自愿…

[论文阅读笔记33] Matching Anything by Segmenting Anything (CVPR2024 highlight)

这篇文章借助SAM模型强大的泛化性&#xff0c;在任意域上进行任意的多目标跟踪&#xff0c;而无需任何额外的标注。 其核心思想就是在训练的过程中&#xff0c;利用strong augmentation对一张图片进行变换&#xff0c;然后用SAM分割出其中的对象&#xff0c;因此可以找到一组图…

Session会话与请求域的区别

session会话和请求域&#xff08;也称为request域&#xff09;都是用于存储和管理用户特定信息的重要概念&#xff0c;但它们在作用范围和生命周期上有显著的不同。 请求域 (Request Domain) 作用范围&#xff1a;请求域是面向单次请求的。每次HTTP请求都会创建一个新的request…

【实战教程】如何使用JMeter来轻松测试WebSocket接口?

1、websocket接口原理 打开网页&#xff1a;从http协议&#xff0c;升级到websocket协议&#xff0c;请求建立websocket连接服务器返回建立成功成功客户端向服务端发送匹配请求服务端选择一个客服上线服务器返回客服id客户端向服务器发送消息服务器推送消息给指定的客服服务器…

EXCEL快速填充空白内容

** EXCEL快速填充空白内容 ** 1.全选所有需要填充的内容&#xff0c;按住电脑的F5或者CTRLG点击定位 2.可以看到空白处被自动选定&#xff0c;之后按电脑和⬆&#xff0c;最后CTRLenter 可以看到空白处已经被填充。

C#——this关键字详情

this关键字 在 C# 中&#xff0c;可以使用 this 关键字来表示当前对象&#xff0c;日常开发中我们可以使用 this 关键字来访问类中的成员属性以及函数。 使用this表示当前类的对象 执行结果 使用 this 关键字串联构造函数 执行结果 使用 this 关键字作为类的索引器 执行结果 …

02逻辑代数与硬件描述语言基础

2.1 逻辑代数&#xff08;简单逻辑的运算&#xff09; 2.2 逻辑函数的卡诺图&#xff08;从图论的角度&#xff09;化简法 2.3 硬件描述语言Verilog HDL基础&#xff08;研究生阶段才用得到&#xff09; 要求&#xff1a; 1、熟悉逻辑代数常用基本定律、恒等式和规则。 2、掌握…

蒸汽架空管道中的关键守护者:滑动管托、导向管托与固定管托

蒸汽架空管道中的关键守护者&#xff1a;滑动管托、导向管托、固定管托与补偿器的重要角色在蒸汽架空管道系统中&#xff0c;每一个组件都扮演着不可或缺的角色&#xff0c;共同确保管道的安全、高效运行。今天&#xff0c;我们就来深入探讨滑动管托、导向管托、固定管托以及补…

用一个实例看如何分享大量照片 续篇二,关于Exif (Exchangeable Image File) - 可交换图像文件

续篇二&#xff1a;说说关于照片隐含的 Exif (Exchangeable Image File) 可交换图像文件 数码照片的Exif 参数有很多&#xff0c;重要的Exif信息&#xff1a;拍摄日期、时间、拍摄器材、GPS信息。 当然这主要对自己的档案有意义&#xff0c;如果放到网上还是建议抹去这些信息。…

50etf期权合约一手多少钱你知道吗?

今天带你了解50etf期权合约一手多少钱你知道吗&#xff1f;50etf期权有不同价值的合约&#xff0c;每手50etf期权合约从几元到几百元再到上千元的都有&#xff0c;具体需要根据投资者选择了什么价值的合约。 50etf期权权利金 50ETF期权合约的权利金是买方需要缴纳的费用&…

Asm动态生成类和get and set方法

asm在解析文件的时候是按照特定顺序进行分析的&#xff0c;首先是visit方法&#xff0c;做类相关的解析&#xff0c;然后是注解&#xff0c;然后是属性&#xff0c;最后才是方法&#xff0c;属性是在所有方法分析前面进行&#xff0c;也就是只有当class文件中的所有属性都遍历完…

GMSB文章五:微生物组差异分析ANCOMBC-2

欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 介绍 微生物的物种差异分析是一项关键的生物信息学任务&#xff0c;旨在识别不同生物群落或样本组…

讯飞星火企业智能体平台正式发布,打造每个岗位专属AI助手

大力财经 | 发布 讯飞星火V4.0来了&#xff01;6月27日&#xff0c;科大讯飞在北京发布讯飞星火大模型V4.0及相关落地应用。讯飞星火V4.0七大核心能力全面提升&#xff0c;整体超越GPT-4 Turbo&#xff0c;在8个国际主流测试集中排名第一&#xff0c;国内大模型全面领先。 大模…

代码随想录算法训练营第三十六天|62.不同路径、 63. 不同路径 II、343.整数拆分(可跳过)、96.不同的二叉搜索树(可跳过)

62.不同路径 题目链接&#xff1a;62.不同路径 文档讲解&#xff1a;代码随想录 状态&#xff1a;还行 思路&#xff1a;当前状态的只有可能是从上面或者左边过来的&#xff0c;所以 dp[i][j] dp[i-1] dp[j-1] 题解&#xff1a; public int uniquePaths(int m, int n) {if (…