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

news2024/10/7 10:00:00

题目:

题解:


/*
数值链表的节点定义。
*/
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.coloradmin.cn/o/2193933.html

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

相关文章

【Canvas与色彩】十六等分多彩隔断圆环

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>隔断圆环Draft5十六等分多彩</title><style type"text…

MyBatis-MP这个ORM框架强过你写的100行SQL,操作简单到不敢相信

MyBatis-MP这个ORM框架强过你写的100行SQL&#xff0c;操作简单到不敢相信 在繁杂的 Java 项目中&#xff0c;如何优雅、高效地进行数据库操作&#xff1f;MyBatis-MP&#xff0c;一个基于 MyBatis 的轻量级 ORM 框架&#xff0c;或许就是你的救星&#xff01;本文将介绍 MyBat…

Android车载——VehicleHal运行流程(Android 11)

1 概述 本篇主要讲解VehicleHal的主要运行流程&#xff0c;包括设置属性、获取属性、订阅属性、取消订阅、持续上报属性订阅等。 2 获取属性流程 2.1 获取属性流程源码分析 作为服务注册到hwServiceManager中的类是VehicleHalManager&#xff0c;所以&#xff0c;CarServic…

第十四章 Redis之全局唯一ID(分布式集群)

目录 一、概念 ‌二、全局唯一ID的生成方法‌ 三、Redis生成全局ID 3.1. 生成策略 3.2. 代码 一、概念 全局唯一ID是指在分布式系统中&#xff0c;每个实体都有一个唯一的标识符&#xff0c;确保在不同的节点或服务之间能够唯一标识一个实体。这种唯一性对于数据的一致性…

软件系统建设方案案例(word原件,文档系统)

文档管理系统建设的主要意义在于提升组织内部文档管理的效率、安全性和便利性。首先&#xff0c;通过集中存储和分类管理&#xff0c;文档管理系统能够迅速检索和共享文件&#xff0c;大幅提高工作效率。其次&#xff0c;系统内置的权限控制功能确保文档的安全&#xff0c;防止…

QT调用最新的libusb库

一&#xff1a;下载libusb文件 下载最新的库的下载网站&#xff1a;https://libusb.info/ 下载&#xff1a; 解压后目录如下&#xff1a; 二&#xff1a;库文件添加QT中 根据自己的编译器选择库&#xff1a; ①将头文件中添加libusb.h ②源文件中添加libusb-1.0.lib ③添加…

Linux:进程的创建、终止和等待

一、进程创建 1.1 fork函数初识 #include pid_t fork(void); 返回值&#xff1a;子进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 调用fork函数后&#xff0c;内核做了下面的工作&#xff1a; 1、创建了一个子进程的PCB结构体、并拷贝一份相同的进程地址…

【C++】继承(万字详细总结)

「前言」 &#x1f308;个人主页&#xff1a; 代码探秘者 &#x1f308;C语言专栏&#xff1a;C语言 &#x1f308;C专栏&#xff1a; C &#x1f308;喜欢的诗句:天行健,君子以自强不息. 前言&#xff1a;面向对象三大特性是&#xff1a;封装、继承、多态&#xff0c;今天的篇…

【社保通-注册安全分析报告-滑动验证加载不正常导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

MES系统在数字化转型中的重要性

实现生产过程的数字化与智能化&#xff1a; 实时监控与数据采集&#xff1a;MES系统通过实时监控和数据采集&#xff0c;将传统的手工记录和管理转变为数字化的自动化过程&#xff0c;实现了生产过程的数字化和智能化管理。这种转变使得生产数据更加准确、及时&#xff0c;为生…

【华为HCIP实战课程六】OSPF邻居关系排错网络子网掩码问题,网络工程师

一、链路上网络和掩码引发的OSPF邻居问题 R3和R4已经建立正常的ospf邻居关系 更改IP地址前R3接口IP地址 interface Serial2/0/0 link-protocol ppp ip address 10.1.34.3 255.255.255.240 [R3-Serial2/0/0]ip address 10.1.88.2 255.255.255.240 更改为10.1.88.2 R3和R4虽…

LabVIEW提高开发效率技巧----点阵图(XY Graph)

在LabVIEW开发中&#xff0c;点阵图&#xff08;XY Graph&#xff09; 是一种强大的工具&#xff0c;尤其适用于需要实时展示大量数据的场景。通过使用点阵图&#xff0c;开发人员能够将实时数据可视化&#xff0c;帮助用户更直观地分析数据变化。 1. 点阵图的优势 点阵图&…

JS | JS中判断数组的6种方法,你知道几个?

目录 1、通过 instanceof 运算符判断 2、通过 constructor 构造函数属性判断 3、通过 Object.prototype.toString.call() 方法判断 4、通过 Array.isArray() 判断 5、通过Array原型链上的 isPrototypeOf() 方法判断 6、通过 Object.getPrototypeOf() 方法判断 因为数组是…

基于51单片机的多路电压测量proteus仿真

地址&#xff1a;https://pan.baidu.com/s/1cpgtfl571DcKfjhKvcKqSA 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectron…

设计模式~~~

简单工厂模式(静态工厂模式) 工厂方法模式 抽象工厂角色 具体工厂角色

分词的艺术:为AI拆解文本

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

基于 springboot vue中学生日常行为评分管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…

鸿蒙next开发者第一课02.DevEcoStudio的使用-习题

【习题】DevEco Studio的使用 通过/及格分80/ 满分100 判断题 1. 如果代码中涉及到一些网络、数据库、传感器等功能的开发&#xff0c;均可使用预览器进行预览。F 正确(True)错误(False) 预览器不能进行传感器等特殊功能的开发,需要使用真机开发 2. module.json5文件中的…

Day03-数据库服务管理语句

Day03-数据库服务管理语句 1、数据库服务语句分类1.1 什么是SQL语句1.2 SQL规范标准1.3 SQL语句分类 2、数据库服务字符设置2.1 为什么要有字符编码设置&#xff08;避免中文乱码&#xff09;--为什么中文会乱码&#xff1f;2.2 数据库中常用的字符编码以及区别2.3 数据库中如何…

Python | Leetcode Python题解之第461题汉明距离

题目&#xff1a; 题解&#xff1a; class Solution:def hammingDistance(self, x, y):ret 0bx, by bin(x)[2:].zfill(32), bin(y)[2:].zfill(32)for i in range(32):if bx[i] ! by[i]:ret 1return ret