手撕LFU

博主介绍:程序喵大人

  • 35- 资深C/C++/Rust/Android/iOS客户端开发
  • 10年大厂工作经验
  • 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
  • 《C++20高级编程》《C++23高级编程》等多本书籍著译者
  • 更多原创精品文章,首发gzh,见文末
  • 👇👇记得订阅专栏,以防走丢👇👇
    😉C++基础系列专栏
    😃C语言基础系列专栏
    🤣C++大佬养成攻略专栏
    🤓C++训练营
    👉🏻个人网站

最近是校招实习面试高峰期,训练营中很多同学都在面试中,有些同学甚至被大厂要求手撕LFU,觉得很离谱。

但其实手撕LFU在面试中已经不少见了,手撕LFU、手撕LRU,在现在的面试中都很常见,大家一定要掌握,平时可以多练几遍。

对应的力扣链接如下:

  • https://leetcode.cn/problems/lfu-cache/
  • https://leetcode.cn/problems/lru-cache/

相关LRU题解如下:

下面通过一个例子,来给大家说一下 LRU 的概念。

假设你是一位图书管理员,你需要管理一个热门书籍借阅专区,空间有限,只能存放一定数量的书籍。读者借阅书籍后需归还,当专区满了,又有新的热门书籍要上架时,你会怎么做呢?

你大概会去查看借阅记录,看哪些书籍被频繁借阅,频繁被借阅(相当于被访问)的书籍会继续留在专区,而那些很久都没有读者借阅(长时间未被访问 )的书籍,会将其从专区移除,放到普通书架,把空间让给新的热门书籍。

上面这个例子,就是我们常说的 LRU 缓存淘汰算法。

既然知道了 LRU 原理,下面我们来看一下题目要求

那我们来拆解一下,需要做哪些工作

  1. 设计一个数据结构,用来存储元素
  2. 维护数据结构里面的元素序列,能够做到需要腾出空间时,可以快速逐出最久未使用的关键字
  3. 能够快速的通过 key 获取 value,也就是做到随机访问

需求已经很明确了,那我们此时应该选择什么数据结构呢?因为需要快速获取 value,并且要 put key 和 value,那么数据结构肯定要有 HashMap。

在 LRU 算法里,我们要维护元素的访问顺序,每次访问一个节点,无论是新节点还是已有节点,都要把它移到有序序列的头部,以此表示它是最新被访问的。

这个有序序列会始终保持从头部到尾部,节点未被访问的时间依次递增。也就是说,序列头部的节点是刚刚被访问过的,而尾部的节点则是最久未被访问的,在需要淘汰元素时,就优先淘汰尾部节点。

以上所述,我们可以使用 **哈希表+链表 **来完成我们的需求。

那应该使用单向链表,还是双向链表呢?

移动节点到链表头部或删除链表尾部节点,都需先删除目标节点。

寻找后继节点时,单双向链表均可通过next指针在 O (1) 时间完成;但寻找前驱节点,单向链表需从头遍历,耗时 O (n),双向链表则能借前向指针在 O (1) 时间找到。因此,为保证操作均在 O (1) 时间内完成,故应选择双向链表,本质是以空间换时间。

好了,我们来看一下,具体是如何存储元素的呢?

从上图可知,我们的 key 为 int,value 为双向链表的节点

好啦,现在我们已经清晰知道了,应该如何设计数据结构,我们进一步根据题目,来了解需求

  1. LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存

解析: HashMap 需要有 size,并初始化 map 的 key 为 int,value 为双向链表的节点

  1. int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1

解析: 如果不存在返回 -1,如果存在,则将该 value 返回,并且将该节点,移到双向链表的头部,移到链表头部的操作我们可以分两步进行

第一步:将节点从当前位置删除

第二步:在链表头部 add 该节点

具体操作如下

这样就实现了,将某节点,移动到头部的操作

  1. void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value 并将其移动到链表头部;如果不存在,则向缓存中插入该组 key-value 同时在链表头部插入该节点。如果插入操作导致关键字数量会超过 capacity ,则应该 逐出 最久未使用的关键字后再插入

解析:这里有两点需要注意,第一点,put 时,假设该节点存在,则需要将其放到头节点。第二点如果超出缓存容量,则需要先删除节点,再在头部插入新节点。

整体思路已经了解,我们来看代码吧

注:代码中也有详细的解析,请认真阅读代码

class LRUCache {
private:
// 链表的节点结构体,因为是双向链表,需要有 perv,next,value,key,
// 这里有人问了,为什么还需要添加 key 呢?
// 因为删去最近最少使用的键值对时,要删除链表的尾节点
// 如果节点中没有存储 key,那么就无法知道,被删除的是那个节点,也无法删除 map 中对应的 key-value
// 此时,我们是先确定要删除的链表节点,再去 map 中删除对应的 key-value
struct DouLink {
int key;
int value;
DouLink* prev;
DouLink* next;
DouLink() : key(0), value(0), prev(nullptr), next(nullptr) {}
DouLink(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
};DouLink* head; // 虚拟头节点
DouLink* tail; // 虚拟尾节点,帮助我们来完成头插法和尾部删除节点
int capacity; // map 的容量
int size; // 当前节点数目
// 我们不要求有序,所以使用 unordered_map 即可,提升性能
std::unordered_map<int, DouLink*> map; 
public:
// 构造函数初始化,只有虚拟头尾节点的双向链表,并配置,缓存容量
LRUCache(int capacity) : capacity(capacity), size(0) {head = new DouLink();tail = new DouLink();head->next = tail;tail->prev = head;
}// 释放
~LRUCache() {DouLink* current = head;while (current != nullptr) {DouLink* nextNode = current->next;delete current;current = nextNode;}
}// 获取节点内容
int get(int key) {auto it = map.find(key);// 未发现返回 -1,符合题目要求if (it == map.end()) {return -1;}// 访问节点DouLink* temp = it->second;// 将最新被访问的节点,移到链表头部moveHead(temp);// 返回值return temp->value;
}
// put 有两种情况,原先是否存该值
void put(int key, int value) {auto it = map.find(key);// 不存在if (it == map.end()) {// 增加新元素前,判断是否需要清理空间(链表尾部节点,长时间未被访问节点)if (size == capacity) {DouLink* newNode = removeTail();map.erase(newNode->key);delete newNode;--size;}// 初始化节点,并执行插入到链表头部DouLink* test = new DouLink(key, value); addHead(test);// map 也执行对应的插入 key-valuemap[key] = test;++size; // 记录当前存储元素数目} else {// 存在该节点,修改节点内容DouLink* temp = it->second;temp->value = value;moveHead(temp);}
}// 封装的操作链表函数,添加到链表头部
void addHead(DouLink* node) {node->next = head->next;head->next->prev = node;head->next = node;node->prev = head;
}
// 封装的操作链表函数,移动到链表头部
void moveHead(DouLink* node) {remove(node); // 删除节点addHead(node); // 将节点添加到头部
}// 删除某节点
void remove(DouLink* node) {node->prev->next = node->next;node->next->prev = node->prev;
}// 删除链表尾部节点,长时间未被访问节点
DouLink* removeTail() {DouLink* temp = tail->prev;remove(temp);return temp;
}
};

码字不易,欢迎大家点赞关注评论,谢谢!


C++训练营

专为校招、社招3年工作经验的同学打造的1V1 C++训练营,量身定制学习计划、每日代码review,简历优化,面试辅导,已帮助多名学员获得大厂offer!

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

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

相关文章

火影bug,未保证短时间数据一致性,拿这个例子讲一下Redis

本文只拿这个游戏的bug来举例Redis&#xff0c;如果有不妥的地方&#xff0c;联系我进行删除 描述&#xff1a;今天在高速上打火影&#xff08;有隧道&#xff0c;有时候会卡&#xff09;&#xff0c;发现了个bug&#xff0c;我点了两次-1000的忍玉&#xff08;大概用了1千七百…

KRaft (Kafka 4.0) 集群配置指南(超简单,脱离 ZooKeeper 集群)还包含了简化测试指令的脚本!!!

docker-compose方式部署kafka集群 Kafka 4.0 引入了 KRaft 模式&#xff08;Kafka Raft Metadata Mode&#xff09;&#xff0c;它使 Kafka 集群不再依赖 ZooKeeper 进行元数据管理。KRaft 模式简化了 Kafka 部署和管理&#xff0c;不需要额外配置 ZooKeeper 服务&#xff0c;…

Admyral - 可扩展的GRC工程自动化平台

文章目录 一、关于 Admyral相关链接资源关键特性 二、安装系统要求 三、快速开始1、启动服务 四、核心功能1、自动化即代码2、AI增强工作流3、双向同步编辑器4、工作流监控5、企业级基础设施 五、示例应用六、其他信息许可证遥测说明 一、关于 Admyral Admyral 是一个基于 Pyt…

DDR在PCB布局布线时的注意事项及设计要点

一、布局注意事项 控制器与DDR颗粒的布局 靠近原则&#xff1a;控制器与DDR颗粒应尽量靠近&#xff0c;缩短时钟&#xff08;CLK&#xff09;、地址/控制线&#xff08;CA&#xff09;、数据线&#xff08;DQ/DQS&#xff09;的走线长度&#xff0c;减少信号延迟差异。 分组隔…

计算机网络-LDP工作过程详解

前面我们已经学习了LDP的基础概念&#xff0c;了解了LDP会话的建立、LDP的标签控制等知识&#xff0c;今天来整体过一遍LDP的一个工作过程&#xff0c;后面我们再通过实验深入学习。 一、LDP标签分发 标签分发需要基于基础的路由协议建立LDP会话&#xff0c;激活MPLS和LDP。以…

解构与重构:自动化测试框架的进阶认知之旅

目录 一、自动化测试的介绍 &#xff08;一&#xff09;自动化测试的起源与发展 &#xff08;二&#xff09;自动化测试的定义与目标 &#xff08;三&#xff09;自动化测试的适用场景 二、什么是自动化测试框架 &#xff08;一&#xff09;自动化测试框架的定义 &#x…

跑不出的循环 | LoveySelf 系列定位

最近开始陷入一轮一轮的循环状态&#xff0c;无奈&#xff0c;只能自我整理一下。23年暑假&#xff0c;在计算机系折腾了一年后&#xff0c;重新打开博客&#xff0c;回想在数学系摸索博客写作的日子&#xff0c;思绪涌上心头&#xff0c;我们决定拾起这份力量。当时觉得 hexo …

Redis最新入门教程

文章目录 Redis最新入门教程1.安装Redis2.连接Redis3.Redis环境变量配置4.入门Redis4.1 Redis的数据结构4.2 Redis的Key4.3 Redis-String4.4 Redis-Hash4.5 Redis-List4.6 Redis-Set4.7 Redis-Zset 5.在Java中使用Redis6.缓存雪崩、击穿、穿透6.1 缓存雪崩6.2 缓冲击穿6.3 缓冲…

一文读懂Python之requests模块(36)

一、requests模块简介 requests模块是python中原生的一款基于网络请求的模块&#xff0c;功能强大&#xff0c;简单便捷且高效 &#xff0c;该模块可以模拟浏览器发送请求&#xff0c;主要包括指定url、发起请求、获取响应数据和持久化存储&#xff0c;包括 GET、POST、PUT、…

WPF之布局流程

文章目录 1. 概述2. 布局元素的边界框3. 布局系统原理3.1 布局流程时序图 4. 测量阶段(Measure Phase)4.1 测量过程4.2 MeasureOverride方法 5. 排列阶段(Arrange Phase)5.1 排列过程5.2 ArrangeOverride方法 6. 渲染阶段(Render Phase)7. 布局事件7.1 主要布局事件7.2 布局事件…

uniapp|获取当前用户定位、与系统设定位置计算相隔米数、实现打卡签到(可自定义设定位置、位置有效范围米数)

基于UniApp阐述移动应用开发中定位功能的实现全流程,涵盖实时定位获取、动态距离计算与自定义位置、有效范围设定等功能。文章提供完整的代码示例与适配方案,适用于社交签到、课堂教室打卡等场景。 目录 引言定位功能在移动应用中的价值(社交、导航、O2O等场景)UniApp跨平台…

Yii2.0 模型规则(rules)详解

一、基本语法结构 public function rules() {return [// 规则1[[attribute1, attribute2], validator, options > value, ...],// 规则2[attribute, validator, options > value, ...],// 规则3...]; }二、规则类型分类 1、核心验证器&#xff08;内置验证器&#xff0…

数据结构(三)——栈和队列

一、栈和队列的定义和特点 栈&#xff1a;受约束的线性表&#xff0c;只允许栈顶元素入栈和出栈 对栈来说&#xff0c;表尾端称为栈顶&#xff0c;表头端称为栈底&#xff0c;不含元素的空表称为空栈 先进后出&#xff0c;后进先出 队列&#xff1a;受约束的线性表&#xff0…

SQL Server 存储过程开发三层结构规范

以下是《SQL Server 存储过程开发三层结构规范》的正式文档结构&#xff0c;适用于企业级数据库应用开发场景&#xff0c;有助于团队协作、代码审查与自动化运维&#xff1a; &#x1f4d8; SQL Server 存储过程开发三层结构规范 一、架构设计总览 三层结构简介 层级命名约定…

接上篇,解决FramePack启动报错:“httpx.ReadError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。“的问题

#工作记录 FramePack部署&#xff08;从PyCharm解释器创建和使用开始&#xff09;保姆级教程-CSDN博客 上篇我们记录到FramePack从克隆到启动调试的保姆级教程&#xff0c;关于启动时会报以下错误的问题&#xff0c;已作出解决&#xff1a; 报错摘录&#xff1a; (.venv) PS F…

ping_test_parallel.sh 并行网络扫描脚本

并行网络扫描脚本分析&#xff1a;提高网络探测效率 引言脚本概述核心代码分析颜色定义与初始化并行处理机制并行执行与进程控制结果处理与统计 技术亮点性能分析结论附录&#xff1a;完整脚本 引言 在网络管理和运维过程中&#xff0c;快速检测网段内主机的在线状态是一项常见…

leetcode 3342. 到达最后一个房间的最少时间 II 中等

有一个地窖&#xff0c;地窖中有 n x m 个房间&#xff0c;它们呈网格状排布。 给你一个大小为 n x m 的二维数组 moveTime &#xff0c;其中 moveTime[i][j] 表示在这个时刻 以后 你才可以 开始 往这个房间 移动 。你在时刻 t 0 时从房间 (0, 0) 出发&#xff0c;每次可以移…

关于vue-office在vue3工程中的引用报错问题

在vue3项目工程中&#xff0c;根据vue-office文档在vue2中的引用&#xff1a; //引入VueOfficeDocx组件 相关样式import VueOfficeDocx from vue-office/docx;import vue-office/docx/lib/index.css; 报错信息&#xff1a; [plugin:vite:import-analysis] Failed to resolve …

【macOS常用快捷键】

以下是 macOS 最常用快捷键列表&#xff0c;按使用频率由高到低分类整理&#xff0c;涵盖日常操作、效率工具及系统控制&#xff0c;助你快速提升使用效率&#xff1a; 一、基础高频操作 快捷键功能说明Command C复制选中内容Command V粘贴Command X剪切Command Z撤销上一…

mdadm 报错: buffer overflow detected

最近跑 blktest (https://github.com/osandov/blktests) 时发现 md/001 的测试失败了 单独执行&#xff0c;最后定位到是 mdadm 命令报错: buffer overflow detected 这个 bug 目前已经修复: https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git/commit/?id827e1870f3205…