【高频面试题】LRU缓存

news2025/7/19 20:54:13

文章目录

    • 1 相关前置知识(OS)
    • 2 面试题 16.25. LRU 缓存
      • 2.1 题面
      • 2.2 示例
      • 2.3 解法1 (双端队列+哈希表)
        • 思路
      • 2.4 解法2
        • 思路
    • 3 参考

1 相关前置知识(OS)

  • 为什么需要页面置换算法:当进程运行时,若其访问的页面不在内存需要将其调入,但内存已无空闲空间时,就需要从内存中调出一页,换出到外存,选择调出哪个页面的算法就称为页面置换算法。页面的换入,换出需要磁盘I/O开销较大,因此要设计一个好的页面置换算法追求更低的缺页率。
  • 常见的页面置换算法:OPT(最佳置换算法) FIFO(先进先出置换算法) LRU(最近最久未使用置换算法) CLOCK(时钟置换算法)
  • 什么是LRU算法:简单来讲就是将一些元素放在一个容量固定的容器中进行存取,由于容器的容量有限,该容器就要保证那些最近才被用到的元素始终在容器内,而将已经很久没有用的元素剔除,实现容器内元素的动态维护。这种算法是一种缓存维护策略,因为缓存空间有限,让缓存中存储的都是最近才被用到的元素可以实现系统缓存的高效运作。

2 面试题 16.25. LRU 缓存

2.1 题面

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作:

  • 获取数据 get 和 写入数据 put
  • 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
  • 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
  • 注意:要求getput的时间复杂度为O(1)

2.2 示例

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

2.3 解法1 (双端队列+哈希表)

思路
  • 使用双端队列维护,队尾是最近使用的,队头是最久未使用的,哈希表则维护key-value,此外注意在get时若元素存在则要重新将该元素移动到队列尾部,put若元素已存在,则要更新value,并将元素调整到队尾。
class LRUCache {
private:
    int capacity;
    std::deque<int> dq; 
    std::unordered_map<int, int> mp;  
public:
    LRUCache(int capacity) : capacity(capacity) {}

    int get(int key) { // 若元素存在则将该元素移到队尾
        if (mp.find(key) != mp.end()) {
            dq.erase(std::remove(dq.begin(), dq.end(), key), dq.end());
            dq.push_back(key);
            return mp[key];
        }
        return -1;
    }

    void put(int key, int value) {
        if (mp.find(key) != mp.end()) {// 如果 key 已存在,更新 value,并调整到队尾
            mp[key] = value;
            dq.erase(std::remove(dq.begin(), dq.end(), key), dq.end());
            dq.push_back(key);
        } else {
            if (dq.size() == capacity) {
                int lru_key = dq.front();
                mp.erase(lru_key);
                dq.pop_front();
            }
            mp[key] = value;
            dq.push_back(key);
        }
    }
};

2.4 解法2

解法1的效率十分低,而且没有满足时间复杂度O(1)的条件,效率只击败了5%,我们想想要如何实现O(1),什么数据结构支持O(1)维护两端元素呢,我们可以用双向链表来实现。

思路
  • 使用双向链表+哈希表维护,表头维护最近使用的元素,表尾维护最久未使用的元素。这里我们要使用双向链表实现在表头添加一个元素、删除某个节点这两个操作。

  • 对于 get 操作,首先判断 key是否存在:如果 key 不存在,则返回 -1;如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。

  • 对于 put 操作,首先判断 key 是否存在:如果 key 不存在,使用 keyvalue 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。

这里引用灵神的题解,大家可以理解的更清晰。

class Node {
public:
    int key, value;
    Node *prev, *next;
    // 构造函数
    Node() : key(0), value(0), prev(nullptr), next(nullptr) {}
    Node(int _key, int _value) : key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

class LRUCache {
private:
    std::unordered_map<int, Node*> cache;
    Node* dummy;    // 头节点 表头维护最近使用的 表尾维护最久未使用的
    int capacity;
    // 删除一个节点
    void remove(Node *p) {
        p->prev->next = p->next;
        p->next->prev = p->prev;
    }

    // 表头添加一个节点
    void push_front(Node *p) {
        p->prev = dummy;
        p->next = dummy->next;
        p->prev->next = p;
        p->next->prev = p;
    }
    Node* get_node(int key) {
        auto it = cache.find(key);
        if (it == cache.end()) return nullptr;
        auto node = it->second;
        remove(node); // 将元素移出
        push_front(node); // 放在表头
        return node;
    }
public:
    LRUCache(int capacity) {
        this->capacity = capacity;
        dummy = new Node();
        dummy->next = dummy;
        dummy->prev = dummy;
    }

    int get(int key) {
        auto node = get_node(key);
        return node ? node->value : -1;
    }

    void put(int key, int value) {
        auto node = get_node(key);
        if (node) { //若存在该元素
            node->value = value; // 更新value
            return ;
        }
        cache[key] = node = new Node(key, value);
        push_front(node); // 放在表头
        if (cache.size() > capacity) { // 超容量
            auto back = dummy->prev;
            cache.erase(back->key);
            remove(back); //去掉尾部元素
            delete back; // 释放内存
        }
    }
};

/**
 * 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);
 */

3 参考

  • 【图解】一张图秒懂 LRU!
  • 面试题 16.25. LRU 缓存(C++【OPT/FIFO/LRU】操作系统页面置换算法一网打尽)

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

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

相关文章

讯联云库项目开发日志(二)AOP参数拦截

目录 利用AOP实现参数拦截: 一、​​HTTP请求进入Controller​&#xff08;发送邮件验证码&#xff09; 二、AOP切面触发 1. 切面拦截&#xff08;GlobalOperactionAspect.class&#xff09; method.getAnnotation()​​ null interceptor 判断​​ 2.参数校验注解 3. 参…

龙虎榜——20250515

上证指数缩量收阴线&#xff0c;个股跌多涨少&#xff0c;上涨波段4月9日以来已有24个交易日&#xff0c;时间周期上处于上涨末端&#xff0c;注意风险。 深证指数缩量收阴线&#xff0c;日线上涨结束的概率在增大&#xff0c;注意风险。 2025年5月15日龙虎榜行业方向分析 一…

卡洛诗,将高端西餐的冗余价值转化为普惠体验

西餐市场正经历一场结构性变革&#xff0c;一二线城市的高端西餐陷入内卷&#xff0c;而下沉市场却因品质与价格断层陷入选择困境——消费者既不愿为高价西餐的面子溢价买单&#xff0c;又难以忍受快餐式西餐的粗糙体验。这一矛盾催生了万亿级的市场真空地带&#xff0c;萨莉亚…

Flutter在键盘的上方加一个完成按钮

有些情况下&#xff0c;输入框在输入键盘弹出后&#xff0c; 需要在键盘的上方显示一个toolbar &#xff0c; 然后 toolbar 上面一个完成按钮&#xff0c;点完成按钮把键盘关闭。 如图&#xff1a; 直接上代码&#xff0c;这样写的好处是&#xff0c;把 TextField 给封装了&…

SQL注入---05--跨站注入

1 权限说明 select * from mysql.user; 这里的Y表示我前面的命令权限为root&#xff0c;n表示不支持root权限 导致结果&#xff1a; 如果为root的话&#xff0c;我就可操作这些命令并且可以进行跨数据库攻击&#xff0c;但是如果不是高权限root就无法执行这些操作 2 root权限…

【免费分享】虚拟机VM(适用于 Windows)17.6.3

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://drive.uc.cn/s/7c4da5cd2af64 【​本章下载二】&#xff1a;https://pan.xunlei.com/s/VOQDkRRKc5OUVTauZezaiDEHA1?pwdpybg# 【百款黑科技】&#xff1a;https://ucnygalh6wle.feishu.cn/wiki/…

2025 后端自学UNIAPP【项目实战:旅游项目】5、个人中心页面:微信登录,同意授权,获取用户信息

一、框架以及准备工作 1、前端项目文件结构展示 2、后端项目文件结构展示 3、登录微信公众平台&#xff0c;注册一个个人的程序&#xff0c;获取大appid&#xff08;前端后端都需要&#xff09;和密钥&#xff08;后端需要&#xff09; 微信公众平台微信公众平台&…

蓝桥杯算法题 -蛇形矩阵(方向向量)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 P…

配置VScodePython环境Python was not found;

Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases. 候试试重启电脑。 在卸载重装python后会出现难以解决的局面&#xff0c;系统变量&#xff0c;命令行&#…

ollama 重命名模型

ollama 重命名模型 ollama list# 查看列表 ollama list # 生成原模型的Modelfile文件 ollama show --modelfile qwen3:32b > Modelfile # 从Modelfile文件创建新的模型 ollama create qwen3 -f Modelfile # 删除原模型 ollama rm qwen3:32b

Qt信号槽机制与UI设计完全指南:从基础原理到实战应用

目录 前言一、信号槽1.1 传参1.2 Qt信号与槽的对应关系1.2.1一对多关系1.2.2 多对一关系 二、Designer三、Layout 布局3.1 基础用法3.2 打破布局3.3 贴合窗口3.4 伸展器&#xff08;Spacer&#xff09;3.5 嵌套布局 四、ui指针五、QWidget六、QLabel 标签使用指南总结 前言 本…

XBL6501/02/03在POE设备上的应用方案

前言&#xff1a; 在当今数字化时代&#xff0c;POE&#xff08;Power over Ethernet&#xff09;设备因其能够通过以太网线同时传输数据和电力而被广泛应用。为了满足这些设备日益增长的电源需求&#xff0c;芯伯乐推出了XBL6501/02/03系列DC-DC电源芯片&#xff0c;为POE设备…

编程题 03-树2 List Leaves【PAT】

文章目录 题目输入格式输出格式输入样例输出样例 题解解题思路完整代码 编程练习题目集目录 题目 Given a tree, you are supposed to list all the leaves in the order of top down, and left to right. 输入格式 Each input file contains one test case. For each case, …

生信小白学Rust-03

语句和表达式 举个栗子&#x1f330; fn add_with_extra(x: i32, y: i32) -> i32 {let x x 1; // 语句let y y 5; // 语句x y // 表达式 } // 语句执行操作 // 表达式会返回一个值 怎么区分呢&#xff0c;目前我的理解是只要返回了值&#xff0c;那它就是表达式 fn…

缺乏需求优先级划分时,如何合理分配资源?

当需求优先级不明确时&#xff0c;合理分配资源的关键在于建立统一评估标准、实施敏捷资源管理、提升团队协作效率、加强跨部门沟通机制。尤其是建立统一评估标准至关重要&#xff0c;它能帮助组织快速判断各项需求的重要性与紧迫性&#xff0c;从而实现资源的动态匹配与有效利…

操作系统学习笔记第3章 内存管理(灰灰题库)

1. 单选题 某页式存储管理系统中&#xff0c;主存为 128KB&#xff0c;分成 32 块&#xff0c;块号为 0、1、2、3、…、31。某作业有 5 块&#xff0c;其页号为 0、1、2、3、4&#xff0c;被分别装入主存的 3、8、4、6、9 块中。有一逻辑地址为 [3, 70]&#xff08;其中方括号中…

详细分析python 中的deque 以及和list 的用法区别

dqque :双端队列&#xff0c;可以快速的从另外一侧追加和推出对象,deque是一个双向链表&#xff0c;针对list连续的数据结构插入和删除进行优化。它提供了两端都可以操作的序列&#xff0c;这表示在序列的前后你都可以执行添加或删除操作。 通过上图可以看出&#xff0c;deque …

Stack overflow

本文来源 &#xff1a;腾讯元宝 Stack Overflow - Where Developers Learn, Share, & Build Careers 开发者学习&#xff0c;分享 通过学习、工作和经验积累等方式&#xff0c;逐步建立和发展自己的职业生涯。 Find answers to your technical questions and help othe…

和为target问题汇总

文章目录 习题377.组合总和 IV494.目标和 和为target的问题&#xff0c;可以有很多种问题的形式的考察&#xff0c;当然&#xff0c;及时的总结与回顾有利于我们熟练掌握这些知识&#xff01; 习题 377.组合总和 IV 377.组合总和 IV 思路分析&#xff1a;通过观察&#xff0…

STM32单片机内存分配详细讲解

单片机的内存无非就两种&#xff0c;内部FLASH和SRAM&#xff0c;最多再加上一个外部的FLASH拓展。在这里我以STM32F103C8T6为例子讲解FLASH和SRAM。 STM32F103C8T6具有64KB的闪存和20KB的SRAM。 一. Flash 1.1 定义 非易失性存储器&#xff0c;即使在断电后&#xff0c;其所…