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;
}相关文章:
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是数值链表的头…...
【AI论文精读12】RAG论文综述2(微软亚研院 2409)P4-隐性事实查询L2
AI知识点总结:【AI知识点】 AI论文精读、项目、思考:【AI修炼之路】 P1,P2,P3 四、隐性事实查询(L2) 4.1 概述 ps:P2有四种查询(L1,L2,L3,L4&…...
SpringBoot中间件Docker
Docker(属于C/S架构软件) 简介与概述 1.Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux …...
计算机毕设选题推荐【大数据专业】
计算机毕设选题推荐【大数据专业】 大数据专业的毕业设计需要结合数据的采集、存储、处理与分析等方面的技能。为帮助同学们找到一个适合且具有实践性的选题,我们为大家整理了50个精选的毕设选题。这些选题涵盖了大数据分析、处理技术、可视化等多个方向࿰…...
Bootstrap 4 多媒体对象
Bootstrap 4 多媒体对象 引言 Bootstrap 4 是目前最受欢迎的前端框架之一,它提供了一套丰富的工具和组件,帮助开发者快速构建响应式和移动设备优先的网页。在本文中,我们将重点探讨 Bootstrap 4 中的多媒体对象(Media Object)组件,这是一种用于构建复杂和灵活布局的强大…...
Springmvc Thymeleaf 标签
Thymeleaf是一个适用于Java的模板引擎,它允许开发者将动态内容嵌入到HTML页面中。在SpringMVC框架中,Thymeleaf可以作为一个视图解析器,使得开发者能够轻松地创建动态网页。以下是关于SpringMVC中Thymeleaf标签的详细介绍: 一、T…...
用java来编写web界面
一、ssm框架整体目录架构 二、编写后端代码 1、编写实体层代码 实体层代码就是你的对象 entity package com.cv.entity;public class Apple {private Integer id;private String name;private Integer quantity;private Integer price;private Integer categoryId;public…...
如何利用Fiddler进行抓包并自动化
首先一般使用Fiddler都是对手机模拟器进行抓包 接下来以MUMU模拟器为例 首先打开Fiddler-->tool-->options-->connection 将要打上的勾都打上,可以看到代理的端口是8888 打开HTTPS选项 把要打的勾打上,这样子才可以接收到HTTPS的包 MUMU打开…...
权重衰减与暂退法——pytorch与paddle实现模型正则化
权重衰减与暂退法——pytorch与paddle实现模型正则化 在深度学习中,模型正则化是一种至关重要的技术,它有助于防止模型过拟合,提高泛化能力。过拟合是指在训练数据上表现良好,但在测试数据或新数据上表现不佳的现象。为了缓解这一…...
MYSQL-windows安装配置两个或多个版本MYSQL
安装第一个mysql很简单,这里不再赘述。主要说说第二个怎么安装,服务怎么配置。 1. 从官网下载第二个MySQL并安装 一般都是免安装版了,下载解压到某个文件目录下(路径中尽量不要带空格或中文),再新建一个my.ini文件(或…...
6、Spring Boot 3.x集成RabbitMQ动态交换机、队列
一、前言 本篇主要是围绕着 Spring Boot 3.x 与 RabbitMQ 的动态配置集成,比如动态新增 RabbitMQ 交换机、队列等操作。二、默认RabbitMQ中的exchange、queue动态新增及监听 1、新增RabbitMQ配置 RabbitMQConfig.java import org.springframework.amqp.rabbit.a…...
【分布式微服务云原生】 探索SOAP协议:简单对象访问协议的深度解析与实践
探索SOAP协议:简单对象访问协议的深度解析与实践 摘要: 在现代分布式系统中,SOAP(简单对象访问协议)扮演着至关重要的角色,提供了一种标准化的方式来实现不同系统间的通信。本文深入探讨了SOAP的工作原理、…...
C语言题目练习2
前面我们知道了单链表的结构及其一些数据操作,今天我们来看看有关于单链表的题目~ 移除链表元素 移除链表元素: https://leetcode.cn/problems/remove-linked-list-elements/description/ 这个题目要求我们删除链表中是指定数据的结点,最终返…...
复变函数与积分变换——留数定理求拉氏逆变换
1.留数定理 若s1,s2,…,sn是F(s)的所有奇点(函数在某个点上的取值无定义或者无限大),且当s→∞时,F(s)→0,则有: 一般地: s1是一级极点,则&#…...
RabbitMQ事务模块
目录 消息分发 负载均衡 幂等性保障 顺序性保障 顺序性保障方案 二号策略:分区消费 三号策略:消息确认机制 四号策略: 消息积压 RabbitMQ集群 选举过程 RabbitMQ是基于AMQP协议实现的,该协议实现了事务机制,要么全部成功,要么全…...
Android终端GB28181音视频实时回传设计探讨
技术背景 好多开发者,在调研Android平台GB28181实时回传的时候,对这块整体的流程,没有个整体的了解,本文以大牛直播SDK的SmartGBD设计开发为例,聊下如何在Android终端实现GB28181音视频数据实时回传。 技术实现 Andr…...
AI金融攻防赛:金融场景凭证篡改检测(DataWhale组队学习)
引言 大家好,我是GISer Liu😁,一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年10月学习赛的AI金融攻防赛学习总结文档。本文主要讲解如何解决 金融场景凭证篡改检测的核心问题,以及解决思路和代码实现过程。希望…...
华为OD机试真题---喊7的次数重排
题目描述 喊7是一个传统的聚会游戏。N个人围成一圈,按顺时针从1到N编号。编号为1的人从1开始喊数,下一个人喊的数字为上一个人的数字加1。但是,当将要喊出来的数字是7的倍数或者数字本身含有7时,不能把这个数字直接喊出来&#x…...
使用阿里巴巴的图
参考链接1 引用彩色图标可参考以下链接 (到第三步 测试图标效果 的时候 还是可以保持之前的写法:<i/sapn class“iconfont icon-xxx”>也会出现彩色的) 参考链接2 阿里巴巴字体使用 也可以直接将官网的代码复制过来到页面的css区域...
【hot100-java】排序链表
链表题。 使用归并排序法。 一图解决。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; thi…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
