LeetCode Hard|【460. LFU 缓存】

力扣题目链接

LFU全称是最不经常使用算法(Least Frequently Used),LFU算法的基本思想和所有的缓存算法一样,一定时期内被访问次数最少的页,在将来被访问到的几率也是最小的。
相较于 LRU 算法,LFU 更加注重于使用的频率。 LRU 其实可以看作是频率为 1 的LFU。

从上图中我们可以看到,LFU是根据频率来进行排序,当我们插入的时候,会把新插入的放到链表的尾部,因为新插入的一定没有出现过的,所以频率都会是1,所以会放在最后

整个算法流程如下:

  1. 如果A没有出现过,那么就会放在双向链表的最后,依次类推,就会是Z、Y。。C、B、A的顺序放到频率为1的链表中。
  2. 当我们新插入 A,B,C 那么A,B,C就会到频率为2的链表中
  3. 如果再次插入A,B那么A,B会在频率为3中。C依旧在2中
  4. 如果此时已经满了 ,新插入一个的话,我们会把最后一个D移除,并插入 6

整体算法如下:

文章目录

  • 自定义类型
  • 定义成员变量
  • 定义私有成员函数
  • 构造函数
  • get方法
  • put方法
  • 总体代码如下

自定义类型

根据题目中的描述,我们除了维护一个键值对之外,还要为这个键值对维护一个计数器:

struct Node {int key, value, freq;Node() : key(0), value(0), freq(1) {}Node(int key, int value) : key(key), value(value), freq(1) {}
};

定义成员变量

很明显,除了 LFUCache 的容量之外,我们还要维护一个最小频率,到时候清除缓存是清除最小频率的最久未使用:

class LFUCache {
private:int capacity_, minFreq_;std::unordered_map<int, Node*> keyNode_; //从键到节点的映射std::unordered_map<int, std::list<Node*>> freqList_; //从频率到节点链表的映射std::unordered_map<Node*, std::list<Node*>::iterator> nodeIter_; //从节点到其在列表中位置的映射
};

定义私有成员函数

这里我们需要一个私有成员函数,也就是更新频率的函数,其实逻辑还是比较好理解的。

    void updateFrequency(Node* node) {int freq = node->freq;auto& nodeList = freqList_[freq]; //获取该频率对应的节点链表nodeList.erase(nodeIter_[node]); // 从该链表中删除该节点,因为该节点的频率注定被改变了// 如果当前频率的节点链表为空,删除该频率并更新最小频率if (nodeList.empty()) {freqList_.erase(freq);if (minFreq_ == freq) minFreq_++; //当前删除的频率链表为最小频率时,更新最小频率}//增加节点的频率,并将其插入到新的频率-节点链表的最前面node->freq++;freqList_[node->freq].push_front(node);nodeIter_[node] = freqList_[node->freq].begin(); //记录每个节点在各自链表中的位置}

构造函数

class LFUCache {
private:...
public:LFUCache(int capacity) : capacity_(capacity), minFreq_(0) {}
};

get方法

    // 获取指定键的值int get(int key) {if (keyNode_.find(key) == keyNode_.end()) {return -1;} else {Node* node = keyNode_[key];updateFrequency(node);return node->value;}}

put方法

	//插入或更新键值对void put(int key, int value) {if (capacity_ == 0) return; //容量为0,直接返回// 更新值、更新频率if (keyNode_.find(key) != keyNode_.end()) {Node* node = keyNode_[key];node->value = value;updateFrequency(node);} else {// 容量已满if (keyNode_.size() == capacity_) {auto& nodeList = freqList_[minFreq_]; //找到最小频率的那个 nodelistNode* node = nodeList.back(); //最小频率的最久未使用节点keyNode_.erase(node->key); //从键到节点的映射中删除该节点nodeIter_.erase(node);  // 从节点到其所属位置映射中删除该节点nodeList.pop_back(); //从获取到的频率链表中删除该节点if (nodeList.empty()) freqList_.erase(minFreq_); //如果频率列表为空,删除该频率链表delete node;}//如果缓存未满的情况下,插入新节点并更新相关映射Node* newNode = new Node(key, value);keyNode_[key] = newNode;freqList_[1].push_front(newNode);nodeIter_[newNode] = freqList_[1].begin();minFreq_ = 1; //重置最小频率}}

总体代码如下

struct Node {int key, value, freq;Node() : key(0), value(0), freq(1) {}Node(int key, int value) : key(key), value(value), freq(1) {}
};class LFUCache {
private:int capacity_, minFreq_;std::unordered_map<int, Node*> keyNode_; //从键到节点的映射std::unordered_map<int, std::list<Node*>> freqList_; //从频率到节点链表的映射std::unordered_map<Node*, std::list<Node*>::iterator> nodeIter_; //从节点到其在列表中位置的映射// 无论使用 get 还是 put 方法都会导致节点频率的增加void updateFrequency(Node* node) {int freq = node->freq;auto& nodeList = freqList_[freq]; //获取该频率对应的节点链表nodeList.erase(nodeIter_[node]); // 从该链表中删除该节点,因为该节点的频率注定被改变了// 如果当前频率的节点链表为空,删除该频率并更新最小频率if (nodeList.empty()) {freqList_.erase(freq);if (minFreq_ == freq) minFreq_++; //当前删除的频率链表为最小频率时,更新最小频率}//增加节点的频率,并将其插入到新的频率-节点链表的最前面node->freq++;freqList_[node->freq].push_front(node);nodeIter_[node] = freqList_[node->freq].begin(); //记录每个节点在各自链表中的位置}
public:LFUCache(int capacity) : capacity_(capacity), minFreq_(0) {}int get(int key) {if (keyNode_.find(key) == keyNode_.end()) {return -1;} else {Node* node = keyNode_[key];updateFrequency(node);return node->value;}}void put(int key, int value) {if (capacity_ == 0) return; //容量为0,直接返回// 更新值、更新频率if (keyNode_.find(key) != keyNode_.end()) {Node* node = keyNode_[key];node->value = value;updateFrequency(node);} else {// 容量已满if (keyNode_.size() == capacity_) {auto& nodeList = freqList_[minFreq_]; //找到最小频率的那个 nodelistNode* node = nodeList.back(); //最小频率的最久未使用节点keyNode_.erase(node->key); //从键到节点的映射中删除该节点nodeIter_.erase(node);  // 从节点到其所属位置映射中删除该节点nodeList.pop_back(); //从获取到的频率链表中删除该节点if (nodeList.empty()) freqList_.erase(minFreq_); //如果频率列表为空,删除该频率链表delete node;}//如果缓存未满的情况下,插入新节点并更新相关映射Node* newNode = new Node(key, value);keyNode_[key] = newNode;freqList_[1].push_front(newNode);nodeIter_[newNode] = freqList_[1].begin();minFreq_ = 1; //重置最小频率}}
};

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

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

相关文章

什么是云原生?(一)

1. 前言 停下手头的工作&#xff0c;让你的同事定义“云原生”一词。你很可能会得到几个不同的答案。 1.1 让我们从一个简单的定义开始&#xff1a; 云原生架构和技术是一种设计、构建和操作在云中构建并充分利用云计算模型的工作负载的方法。 1.2 云原生计算基金会给出了官方…

简单的 CompletableFuture学习笔记

简单的 CompletableFuture学习笔记 这里记录一下自己学习的内容&#xff0c;简单记录一下方便后续学习&#xff0c;内容部分参考 CompletableFuture学习博客 1. CompletableFuture简介 在api接口调用时间过长&#xff0c;调用过多外围接口时&#xff0c;为了提升性能&#x…

Android 12系统源码_多屏幕(一)多屏幕设备显示Activity

前言 分屏&#xff1a;是指一个屏幕分出多个窗口&#xff0c;分别显示不同应用的界面&#xff0c;这在当前的手机设备中很常见。多屏&#xff1a;是指一个设备存在多个屏幕&#xff0c;这些可能是虚拟屏幕或者实体硬件屏幕&#xff0c;不同的应用同时显示在不同的屏幕中&#…

SpringBoot多数据源事务处理

多数据源时,一般会配置多个事务管理器 Spring编程式 第二种方式 不可能去同一个方法上写两个事务注解 不允许 SpringBoot 2.6.0之后禁止自己注入自己 本来可以自己注入自己去调用 (为什么要自己注入自己调用,AOP代理,类不是自己写的类) 最简单方式 引入 <dependency&…

【极速前进】20240706-24240714:用于Agent的树搜、理解LLM的语种困惑、事实知识抽取微调、Quiet-STaR

相关博客 【极速前进】20240706-24240714&#xff1a;用于Agent的树搜、理解LLM的语种困惑、事实知识抽取微调、Quiet-STaR 【极速前进】20240615-20240623&#xff1a;Zipper融合模态、VideoLLM视频理解、WebAgent可以自我改善、Nemotron-4、AnyGPT统一模态 【极速前进】20240…

【秋招突围】2024届校招-拼多多笔试题-第一套

🍭 大家好这里是 大厂笔试突围,一起备战秋招笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🌻 听说本周PDD的笔…

与用户有关的接口

1.获取用户详细信息 跟着黑马程序员继续学习SpringBoot3Vue3 用户登录成功之后跳转到首页&#xff0c;需要获取用户的详细信息 打开接口文档 使用Token令牌解析得到用户名 我们需要根据用户名查询用户&#xff0c;获取详细信息 但是请求参数是无&#xff0c;由于都需要携…

基于微信小程序的小学生口算练习系统

摘 要 随着当今网络的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;小程序也逐步进入人们的生活&#xff0c;给我们生活或者工作提供了新的方向新的可能。 本毕业设计的内容是设计实现一个小学生口算练习的小程序。微信开发者是以java语言进行…

2023 江苏省第一届数据安全技术应用职业技能竞赛 决赛 部分wp

文章目录 一、前言比赛平台全貌题目附件及工具下载&#xff08;123网盘&#xff09; 二、参考文章三、题目&#xff08;解析&#xff09;一、内存取证-MemoryLife1、请给出内存镜像中黑客使用工具对外连接的IP地址及端口号是___________。&#xff08;格式为IP_PORT&#xff09…

app抓包 burp配置

证书导出 模拟器安装证书 点击安装证书 将证书直接拖进来就行 配置代理 打开浏览器抓包

用python创建极坐标平面

极坐标的介绍 http://t.csdnimg.cn/ucau3http://t.csdnimg.cn/ucau3这个文章里可以知道极坐标的基本知识&#xff0c;接下来实现极坐标的绘制 PolarPlane 是 Manim&#xff08;一个用于数学动画的Python库&#xff09;中的一个类&#xff0c;用于创建极坐标平面。与笛卡尔…

开发android app用于移远模块读写IMEI 模组EC200DEULA-D08-SNNDA 支持socket连接读写IMEI

开放权限 adb kill-serveradb rootadb shell setenforce 0adb install -t app-debug.apkadb shell am start -n com.azhon.spplus/.MainActivity::F310A_WriteIMEI -DWadb.exe forward tcp:5902 tcp:5902pause写读IMEI ADB socket协议 TCP 127.0.0.1:5902 PC与终端APP之间 j…

大放异彩!东软医疗DSA闪耀欧洲

欧洲&#xff0c;作为影像技术的发源地&#xff0c;当下正面临经济发展和人口老龄化的双重考验&#xff0c;对医疗技术的革新与升级需求也愈发迫切。中国高端医疗装备品牌东软医疗&#xff0c;凭借深厚的创新底蕴与对临床需求的精准把握&#xff0c;深耕欧洲市场二十余年&#…

数据结构——排序(2):选择排序+交换排序

目录 一、选择排序 &#xff08;1&#xff09;直接选择排序 ①思路 ②过程图示 ③代码实现 ④代码解释 ⑤优化 1.代码实现 2.过程图示 3.代码解释 4.注意 ⑥直接选择排序的复杂度 &#xff08;2&#xff09;堆排序 ①注意 ②代码实现 二、交换排序 &#xff08…

Vue前端服务加密后端服务解密--AES算法实现

在实际项目中考虑到用户数据的安全性&#xff0c;在用户登录时&#xff0c;前端需要对用户密码加密&#xff08;防止用户密码泄露&#xff09;&#xff0c;服务端收到登录请求时先对密码进行解密&#xff0c;然后再进行用户验证登操作。本文 AES ECB 模式来实现前端机密后端解密…

GPT-5:未来已来,你准备好了吗?

GPT-5 一年半后发布&#xff1f;对此你有何期待&#xff1f; IT之家6月22日消息&#xff0c;在美国达特茅斯工程学院周四公布的采访中&#xff0c;OpenAI首席技术官米拉穆拉蒂被问及GPT-5是否会在明年发布&#xff0c;给出了肯定答案并表示将在一年半后发布。此外&#xff0c;穆…

数据库基础知识

数据库基础知识 主流的数据库连接MySQL理解mysql和mysqld和数据库简单对数据库操作MySQL构架SQL分类存储引擎总结 主流的数据库 SQL Sever&#xff1a; 微软的产品&#xff0c;.Net程序员的最爱&#xff0c;中大型项目。Oracle&#xff1a; 甲骨文产品&#xff0c;适合大型项目…

【Linux网络】网络层协议:IP

本篇博客整理了 TCP/IP 分层模型中网络层的 IP 协议&#xff0c;旨在让读者更加深入理解网络协议栈的设计和网络编程。 目录 一、网络层 二、IP 报头 1&#xff09;报头与有效载荷的分离 2&#xff09;有效载荷的上交 3&#xff09;源 IP 与目的 IP 4&#xff09;生存时间…

学习笔记 韩顺平 零基础30天学会Java(2024.8.7)

P481 Math方法 利用random返回一个[2,7]之间的随机数&#xff1a; 因为random只能返回[0,1)之间的随机数&#xff0c;因此做一下处理&#xff1a;[(int)(a), (int) (aMath.random()*(b-a1))]&#xff0c;对于Math.random()*(b-a1)&#xff0c;其中b-a1&#xff0c;它乘上[0,1)相…

第R3周:天气预测

本文为365天深度学习训练营 中的学习记录博客原作者&#xff1a;K同学啊 任务说明&#xff1a;该数据集提供了来自澳大利亚许多地点的大约 10 年的每日天气观测数据。需要做的是根据这些数据对RainTomorrow进行一个预测&#xff0c;这次任务任务与以往的不同&#xff0c;增加了…