C语言 | Leetcode C语言题解之第460题LFU缓存

题目:

题解:


/*
数值链表的节点定义。
*/
typedef struct ValueListNode_s
{int key;int value;int counter;struct ValueListNode_s *prev;struct ValueListNode_s *next;
}
ValueListNode;/*
计数链表的节点定义。
其中,head是数值链表的头节点,对应的是最新的数值节点。
环形链表,head->prev实际就是tail,对应的就是最久未使用的节点。
*/
typedef struct CounterListNode_s
{ValueListNode *head;struct CounterListNode_s *prev;struct CounterListNode_s *next;
}
CounterListNode;/*
对象结构定义。
capacity:           总的容量。
currentCounter:     当前已有的key的数量。
keyHash:            key的哈希数组,为空表示这个key对应数值不存在。
counterHash:        counter的哈希数组,为空表示这个counter对应的链表不存在。
head:               计数链表的头节点。
*/
typedef struct
{int capacity;int currentCounter;ValueListNode **keyHash;CounterListNode **counterHash;CounterListNode *head;
}
LFUCache;/*
几个自定义函数的声明,具体实现见下。
*/
extern void removeValueNode(CounterListNode *counterNode, ValueListNode *valueNode);
extern void insertValueNode(CounterListNode *counterNode, ValueListNode *valueNode);
extern void removeCounterNode(LFUCache *obj, CounterListNode *counterNode);
extern void insertCounterNode(LFUCache *obj, CounterListNode *counterPrev, CounterListNode *counterNode);/*
创建对象。
*/
LFUCache *lFUCacheCreate(int capacity)
{LFUCache *obj = (LFUCache *)malloc(sizeof(LFUCache));/* 总容量就等于入参capacity,当前已有的key的数量初始化为0。 */obj->capacity = capacity;obj->currentCounter = 0;/* key的取值范围是[0, 10^5],共100001个。用calloc代替malloc,即包含了初始化为空的步骤。 */obj->keyHash = (ValueListNode **)calloc(100001, sizeof(ValueListNode *));/* 题目给的操作次数上限是2*10^5。同上,用calloc代替malloc,包含了初始化为空的步骤。 */obj->counterHash = (CounterListNode **)calloc(200001, sizeof(CounterListNode *));/* 刚开始时,计数链表为空。 */obj->head = NULL;return obj;
}/*
获取指定key的数值。
value:          想要获取key对应的数值,初始化为-1,假如获取不到,就返回这个-1。
valueNode:      从keyHash中直接获取的数值链表节点。
counterNode:    在计数加一之前,这个数值节点当前所处的计数链表。
counterNew:     在计数加一之后,这个数值节点想要加入的新计数链表。
*/
int lFUCacheGet(LFUCache *obj, int key)
{int value = -1;ValueListNode *valueNode = obj->keyHash[key];CounterListNode *counterNode = NULL, *counterNew = NULL;/* 对应的key存在数值时,才需要返回其数值,否则返回-1。 */if(NULL != valueNode){/* 要返回的数值。 */value = valueNode->value;/* 这个节点当前在哪一个计数链表节点中。 */counterNode = obj->counterHash[valueNode->counter];/* 数值的计数加一。以及计数加一之后,它想要加入的新的计数链表节点。 */valueNode->counter++;counterNew = obj->counterHash[valueNode->counter];/* 把数值节点从旧的链表中移除。 */removeValueNode(counterNode, valueNode);/* 如果这个新的计数节点还不存在,则新建一个节点。 */if(NULL == counterNew){counterNew = (CounterListNode *)calloc(1, sizeof(CounterListNode));obj->counterHash[valueNode->counter] = counterNew;/* 新建计数节点,加到counterNode的后方。 */insertCounterNode(obj, counterNode, counterNew);}/* 如果旧的计数节点中的数值链表变为空,则旧的计数节点也需要从计数链表中移除。 */if(NULL == counterNode->head){removeCounterNode(obj, counterNode);free(counterNode);obj->counterHash[valueNode->counter - 1] = NULL;}/* 把数值节点加入到新的链表中。 */insertValueNode(counterNew, valueNode);}return value;
}/*
赋值指定key的数值。
keyRemove:          要被移除的键值。
valueNode:          指定的key对应的数值节点。
valueRemove:        可能被移除的数值节点。
counterNode:        在计数加一之前,这个数值节点当前所处的计数链表。
counterNew:         在计数加一之后,这个数值节点想要加入的新计数链表。
*/
void lFUCachePut(LFUCache *obj, int key, int value)
{int keyRemove = 0;ValueListNode *valueNode = obj->keyHash[key], *valueRemove = NULL;CounterListNode *counterNode = NULL, *counterNew = NULL;/* 总容量为0的话,什么都不需要做。 */if(0 == obj->capacity){return;}/* 如果这个key值已经存在,则修改其数值。 */if(NULL != valueNode){/* 修改新的数值。 */valueNode->value = value;/* 这个节点当前在哪一个计数链表节点中。 */counterNode = obj->counterHash[valueNode->counter];/* 数值的计数加一。以及计数加一之后,它想要加入的新的计数链表节点。 */valueNode->counter++;counterNew = obj->counterHash[valueNode->counter];/* 把数值节点从旧的链表中移除。 */removeValueNode(counterNode, valueNode);/* 如果这个新的计数节点还不存在,则新建一个节点。 */if(NULL == counterNew){counterNew = (CounterListNode *)calloc(1, sizeof(CounterListNode));obj->counterHash[valueNode->counter] = counterNew;/* 新建计数节点,加到counterNode的后方。 */insertCounterNode(obj, counterNode, counterNew);}/* 如果旧的计数节点中的数值链表变为空,则旧的计数节点也需要从计数链表中移除。 */if(NULL == counterNode->head){removeCounterNode(obj, counterNode);free(counterNode);obj->counterHash[valueNode->counter - 1] = NULL;}/* 把数值节点加入到新的链表中。 */insertValueNode(counterNew, valueNode);}/* 否则,新建一个键值。 */else{/* 如果没有满总量,则数量加一。 */if(obj->capacity > obj->currentCounter){obj->currentCounter++;}/* 否则,先把最近最久未使用的键移除。 */else{/* 要删除的数值节点所在的计数节点,一定是计数最少的那个counterNode,即头节点。 */counterNode = obj->head;/* 要被移除的节点,是数值链表的尾节点。 */valueRemove = counterNode->head->prev;keyRemove = valueRemove->key;/* 把它从链表中移除。 */removeValueNode(counterNode, valueRemove);/* 如果计数节点中的数值链表变成空,则也移除这个计数节点。 */if(NULL == counterNode->head){removeCounterNode(obj, counterNode);free(counterNode);obj->counterHash[valueRemove->counter] = NULL;}free(valueRemove);obj->keyHash[keyRemove] = NULL;}/* 新建一个数值节点。 */valueNode = (ValueListNode *)calloc(1, sizeof(ValueListNode));valueNode->key = key;valueNode->value = value;valueNode->counter = 1;obj->keyHash[key] = valueNode;/* 要新加入的链表。新出现的数值,计数肯定是1。 */counterNew = obj->counterHash[1];/* 如果这个计数节点还不存在,则新建一个。 */if(NULL == counterNew){counterNew = (CounterListNode *)calloc(1, sizeof(CounterListNode));obj->counterHash[1] = counterNew;/* counter为1的计数节点,肯定是加到头部的。 */insertCounterNode(obj, NULL, counterNew);}/* 把数值节点加入到新的链表中。 */insertValueNode(counterNew, valueNode);}return;
}/*
释放对象。
*/
void lFUCacheFree(LFUCache *obj)
{CounterListNode *counterNode = obj->head, *counterNext = NULL;ValueListNode *valueNode = NULL, *valueNext = NULL;/* 逐个释放计数链表的每个节点。 */while(NULL != counterNode){counterNext = counterNode->next;/* 释放每个计数链表节点下面的数值链表。环形链表的循环,使用do、while语句。 */valueNode = counterNode->head;do{valueNext = valueNode->next;free(valueNode);valueNode = valueNext;}while(counterNode->head != valueNode);free(counterNode);counterNode = counterNext;}/* 释放key的哈希数组。 */free(obj->keyHash);/* 释放counter的哈希数组。 */free(obj->counterHash);/* 释放对象。 */free(obj);return;
}/*
几个自定义函数的具体实现。
主要是双向链表、双向循环链表的节点添加、删除的操作,保证操作前后仍然是双向链表、双向循环链表。
*//*
把数值节点从数值链表中删除。
*/
void removeValueNode(CounterListNode *counterNode, ValueListNode *valueNode)
{/* 如果这个被删除节点是链表中的唯一一个,则删除之后直接为空链表。 */if(valueNode->next == valueNode){counterNode->head = NULL;}/* 否则把它的前后两个节点连接起来。 */else{valueNode->prev->next = valueNode->next;valueNode->next->prev = valueNode->prev;/* 如果删掉的就是头节点,则新的头节点的位置往后挪一位。 */if(counterNode->head == valueNode){counterNode->head = valueNode->next;}}return;
}/*
把数值节点加入到数值链表头部。
*/
void insertValueNode(CounterListNode *counterNode, ValueListNode *valueNode)
{ValueListNode *tail = NULL;/* 如果本身是空链表,则它是其中唯一节点。 */if(NULL == counterNode->head){valueNode->prev = valueNode;valueNode->next = valueNode;}/* 否则就把它插入到原来的头尾之间。 */else{tail = counterNode->head->prev;valueNode->prev = tail;valueNode->next = counterNode->head;counterNode->head->prev = valueNode;tail->next = valueNode;}/* 它成为新的头节点。 */counterNode->head = valueNode;return;
}/*
把计数节点从计数链表中删除。
*/
void removeCounterNode(LFUCache *obj, CounterListNode *counterNode)
{/* 如果删除的本身是头节点,则头节点将变为下一个。 */if(obj->head == counterNode){obj->head = counterNode->next;if(NULL != obj->head){obj->head->prev = NULL;}}/* 否则,把它的前后两个节点连起来。不是头节点的话,prev肯定存在,next可能为空。 */else{counterNode->prev->next = counterNode->next;if(NULL != counterNode->next){counterNode->next->prev = counterNode->prev;}}return;
}/*
把一个新的计数节点加入到计数链表指定节点counterPrev的后方。
如果counterPrev为空,则表示加到链表头。
*/
void insertCounterNode(LFUCache *obj, CounterListNode *counterPrev, CounterListNode *counterNode)
{/* 如果counterPrev为空,说明是加入到头节点的位置。 */if(NULL == counterPrev){counterNode->prev = NULL;counterNode->next = obj->head;if(NULL != obj->head){obj->head->prev = counterNode;}obj->head = counterNode;}/* 否则插入到counterPrev和counterPrev->next之间。 */else{counterNode->prev = counterPrev;counterNode->next = counterPrev->next;if(NULL != counterPrev->next){counterPrev->next->prev = counterNode;}counterPrev->next = counterNode;}return;
}

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

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

相关文章

腾讯云Android 与 iOS 相关

移动端(Android/iOS)支持哪几种系统音量模式? 支持2种系统音量类型,即通话音量类型和媒体音量类型: 通话音量:手机专门为通话场景设计的音量类型,使用手机自带的回声抵消功能,音质…

java使用socket模拟咖啡馆场景,客户与服务器多线程交互场景

java使用socket模拟咖啡馆场景,客户与服务器多线程交互场景 任务的目标是使用客户机 - 服务器架构构建一个“虚拟咖啡馆”系统每个客户端应用程序将扮演 Customer 谁想点茶或咖啡。服务器应用程序将扮演咖啡馆虚拟的角色 Barista 负责处理订单,准备茶和啡&#xff…

OpenCV-风格迁移

文章目录 一、原理二、关键步骤三、实现方法四、可选参数五、示例代码六、总结 OpenCV中的风格迁移是一种计算机视觉技术,它允许用户将一种图像的风格转移到另一幅图像上,从而创造出具有独特美学效果的新图像。这种技术在艺术、设计和娱乐等领域有着广泛…

elementui中table标题和内容插槽

代码 <template> <el-table :data"tableData"> <el-table-column> <template slot"header" slot-scope"scope"> <el-tooltip content"这是列的提示信息" placement"top"> <span>…

谷歌浏览器 文件下载提示网络错误

情况描述&#xff1a; 谷歌版本&#xff1a;129.0.6668.90 (正式版本) &#xff08;64 位&#xff09; (cohort: Control)其他浏览器&#xff0c;比如火狐没有问题&#xff0c;但是谷歌会下载失败&#xff0c;故推断为谷歌浏览器导致的问题小文件比如1、2M会成功&#xff0c;大…

【LeetCode】动态规划—95. 不同的二叉搜索树 II(附完整Python/C++代码)

动态规划—95. 不同的二叉搜索树 II 题目描述前言基本思路1. 问题定义二叉搜索树的性质&#xff1a; 2. 理解问题和递推关系递归构造思想&#xff1a;状态定义&#xff1a;递推公式&#xff1a;终止条件&#xff1a; 3. 解决方法递归 动态规划方法&#xff1a;伪代码&#xff…

sentinel dashboard改造落地设计实现解释(一)-分布式fetcher和metrics存储/搜索

背景 微服务是目前java主流架构,微服务架构技术栈有,服务注册中心,网关,熔断限流,服务同学,配置中心等组件,其中,熔断限流主要3个功能特性,限流,熔断,快速失败。Sentinel是阿里开源的熔断限流组件,sentinel dashboard是演示级别,表现在metrics采集是单机版,metri…

Linux内核 -- 文件系统之超级块 super_operations 字段作用与用法

struct super_operations 字段的作用与用法 super_operations 是 Linux 内核中定义的一个结构体&#xff0c;用于描述与文件系统超级块 (super_block) 相关的操作。这些操作涵盖了文件系统的创建、删除、挂载、卸载等。 结构体定义 struct super_operations {struct inode *…

如何使用vscode的launch.json来debug调试

1、创建一个launch.json文件 选择Python Debugger&#xff0c;再选择Python文件&#xff0c;创建处理如下 默认有下面五个参数 "name": "Python Debugger: Current File","type": "debugpy","request": "launch"…

C++ struct 和 class 的区别、C++结构体和C结构体的区别等基础知识。

特性C structC classC struct默认访问控制符publicprivate全部公开支持成员函数是是否继承支持是是否模板支持是是否运算符重载是是否 在C中&#xff0c;struct 和 class 是两种非常相似的用户定义类型&#xff0c;二者有一些关键区别。C的 struct 与 C 语言中的 struct 也存在…

金九银十软件测试面试题(800道)

今年你的目标是拿下大厂offer&#xff1f;还是多少万年薪&#xff1f;其实这些都离不开日积月累的过程。 为此我特意整理出一份&#xff08;超详细笔记/面试题&#xff09;它几乎涵盖了所有的测试开发技术栈&#xff0c;非常珍贵&#xff0c;人手一份 肝完进大厂 妥妥的&#…

【LeetCode】动态规划—123. 买卖股票的最佳时机 III(附完整Python/C++代码)

动态规划—123. 买卖股票的最佳时机 III 题目描述前言基本思路1. 问题定义2. 理解问题和递推关系状态定义&#xff1a;状态转移公式&#xff1a;初始条件&#xff1a; 3. 解决方法动态规划方法伪代码&#xff1a; 4. 进一步优化5. 小总结 Python代码Python代码解释 C代码C代码解…

07_23 种设计模式之《桥接模式》

文章目录 一、桥接模式基础知识二、桥接模式实战应用 一、桥接模式基础知识 桥接模式定义&#xff1a;将抽象部分与它的实现部分分离&#xff0c;使它们都可以 独立地变化。桥接模式的结构图如下&#xff1a; Abstraction&#xff1a;抽象部分的接口。 RefinedAbstraction&…

Python基础之List列表用法

1、创建列表 names ["张三","李四","王五","Mary"] 2、列表分片 names[1]&#xff1a;获取数组的第2个元素。 names[1:3]&#xff1a;获取数组的第2、第3个元素。包含左侧&#xff0c;不包含右侧。 names[:3]等同于names[0:3]&…

List子接口

1.特点&#xff1a;有序&#xff0c;有下标&#xff0c;元素可以重复 2.方法&#xff1a;包含Collection中的所有方法&#xff0c;还包括自己的独有的方法&#xff08;API中查找&#xff09; 还有ListIterator&#xff08;迭代器&#xff09;&#xff0c;功能更强大。 包含更多…

llava1.5论文阅读

Improved Baselines with Visual Instruction Tuning 通过视觉指令微调增强的基线方法 论文摘要&#xff1a; 我们发现&#xff0c;LLaVA中的全连接视觉语言连接器非常强大且数据效率高。 3.3 数据和模型的scaling 受到将线性投影转变为多层感知机&#xff08;MLP&#xff0…

支付宝支付Java+uniapp支付宝小程序

JS&#xff1a; request.post(/vip/pay, {//这是自己写的java支付接口id: this.vipInfo.id,payWay: alipay-mini}).then((res) > {let success (res2) > {//前端的支付成功回调函数this.$refs.popup.close();// 支付成功刷新当前页面setTimeout(() > {this.doGetVipI…

机器学习/数据分析--用通俗语言讲解时间序列自回归(AR)模型,并用其预测天气,拟合度98%+

时间序列在回归预测的领域的重要性&#xff0c;不言而喻&#xff0c;在数学建模中使用及其频繁&#xff0c;但是你真的了解ARIMA、AR、MA么&#xff1f;ACF图你会看么&#xff1f;&#xff1f; 时间序列数据如何构造&#xff1f;&#xff1f;&#xff1f;&#xff0c;我打过不少…

读书笔记 - 虚拟化技术 - 0 QEMU/KVM概述与历史

《QEMU/KVM源码解析与应用》 - 王强 概述 虚拟化简介 虚拟化思想 David Wheeler&#xff1a;计算机科学中任何问题都可以通过增加一个中间层来解决。 虚拟化思想存在与计算机科学的各个领域。 主要思想&#xff1a;通过分层将底层的复杂&#xff0c;难用的资源虚拟抽象为简…

32单片机 低功耗模式

以下是一个基于STM32的低功耗模式示例代码&#xff0c;展示如何将STM32微控制器置于低功耗模式&#xff0c;并在特定条件下唤醒它。这个示例使用的是STM32 HAL库。 ### 示例代码&#xff1a;进入睡眠模式并使用外部中断唤醒 c #include "stm32f4xx_hal.h" // 函数声明…