LFU的实现

2023-10-27

题目内容

实现一个 LFUCache 类,三个接口:

  • LFUCache(int capacity) 创建一个大小为 capacity 的缓存
  • get(int key) 从缓存中获取键为 key 的键值对的 value
  • put(int key, int value) 向缓存中添加键值对 (key, value)

要求 getput 的均摊时间复杂度为 O ( 1 ) O(1) O(1)

题解

对于 get 操作,我们需要快速获取到 key 对应的键值对,哈希表可以解决。
对于 put 操作,我们需要快速 put 一个键值对,也可以用哈希表解决。

但是问题在于,我们 getput 时,需要维护每个键的使用次数。

LRU 的实现只需要将每个当前使用的键值对移到链表头,当前使用的键值对在下一次操作前必然是最近最少使用的。

而 LFU 并非如此,当前一个键值对的使用只会增加当前操作的键的使用次数。

所以对于 LFU ,我们需要对每个使用次数都维护一个 LRU 。然后按使用次数从小到大维护一个链表。

相当于外部是一个单调递增的链表,每个链表结点是一个 LRUCache

如此

  • 需要用哈希表 key2LRUNode 来维护 keyLRUNode
  • 需要用哈希表 cnt2IncNode 来维护使用次数 cntIncNode

key2LRUNode 是为了快速找到一个 key 对应的结点
cnt2IncNode 是为了根据使用次数快速找到其对应的结点,从而方便更新一个 LRUNode 的使用次数。

LRUNode 操作的定义

struct LRUNode {
    LRUNode* prev;
    LRUNode* next;
    int key;
    int val;
    int cnt;
    LRUNode(int key, int val): key(key), val(val), prev(nullptr), next(nullptr) {}
};

void removeLRUNodeFromLinklist(LRUNode* node) {
    node->prev->next = node->next;
    node->next->prev = node->prev;
}

void insertLRUNodeToLinklist(LRUNode* node, LRUNode* head) {
    node->next = head->next;
    head->next->prev = node;
    head->next = node;
    node->prev = head;
}

LRUCache 操作的定义

class LRUCache {
    void removeLRUNodeFromHashTable(LRUNode* node) {
        if (!key2LRUNode.count(node->key)) return;
        key2LRUNode.erase(node->key);
    }

    void insertLRUNodeToHashTable(LRUNode* node) {
        key2LRUNode[node->key] = node;
    }
public:
    LRUCache() {
        size_ = 0;
        head = new LRUNode(-1, -1);
        tail = new LRUNode(-2, -1);
        head->next = tail;
        head->prev = tail;
        tail->next = head;
        tail->prev = head;
    }

    void put(int key, int value) {
        if (!key2LRUNode.count(key)) {
            LRUNode* newLRUNode = new LRUNode(key, value);
            insertLRUNodeToLinklist(newLRUNode, head);
            insertLRUNodeToHashTable(newLRUNode);
            size_ += 1;
        } else {
            LRUNode* node = key2LRUNode[key];
            node->val = value;
            removeLRUNodeFromLinklist(node);
            insertLRUNodeToLinklist(node, head);
            insertLRUNodeToHashTable(node);
        }
    }

    void del(int key) {
        if (!key2LRUNode.count(key)) return;
        auto node = key2LRUNode[key];
        removeLRUNodeFromLinklist(node);
        removeLRUNodeFromHashTable(node);
        size_ -= 1;
    }

    LRUNode* pop() {
        if (empty()) return nullptr;
        LRUNode* node = tail->prev;
        del(node->key);
        return node;
    }

    bool empty() const  {
        return size_ == 0;
    }

private:
    int size_;
    unordered_map<int, LRUNode*> key2LRUNode;
    LRUNode* head;
    LRUNode* tail;
};

IncNode 的实现

struct IncNode {
    int key;                // key, 对应的次数
    IncNode* prev;
    IncNode* next;
    LRUCache* lru;
    IncNode(int key): key(key), prev(nullptr), next(nullptr), lru(new LRUCache()) {}
};

void removeIncNodeFromLinklist(IncNode* node) {
    node->prev->next = node->next;
    node->next->prev = node->prev;
}

void insertIncNodeFromLinklist(IncNode* node, IncNode* head) {
    node->next = head->next;
    head->next->prev = node;
    head->next = node;
    node->prev = head;
}

LFUCache 的 get 实现

int get(int key) {
    // 如果不存在当前 key ,直接返回 -1
    if (!key2LRUNode.count(key)) return -1;

    // 找到 key 对应的 LRUNode,value 就是 node->val
    LRUNode* node = key2LRUNode[key];
    int ans = node->val;

    // 找到 LRUNode 对应的 IncNode
    IncNode* cur = cnt2IncNode[node->cnt];
    // 为插入 node 到新的一层的前一个节点做准备
    IncNode* pre = cur->prev;

    LRUCache* lru = cur->lru;
    // 将 LRUNode 从 IncNode 对应的 lru 中移除
    lru->del(node->key);
    if (lru->empty()) {
        // 如果 lru 为空,将 cur 这个 IncNode 移除
        removeIncNodeFromHashTable(cur);
        removeIncNodeFromLinklist(cur);
    } else {
        // 如果 cur 对应的 lru 移除 node 后不为空,则 node 新到的计数 IncNode 的前一个就是 cur
        pre = cur;
    }

    // 添加到新的 lru 中
    node->cnt += 1;
    IncNode* incNode;
    // 如果新的 lru 不存在,就创建
    if (!cnt2IncNode.count(node->cnt)) {
        incNode = new IncNode(node->cnt);
        incNode->lru->put(node->key, node->val);
        insertIncNodeFromLinklist(incNode, pre);
    } else {
        incNode = cnt2IncNode[node->cnt];
        incNode->lru->put(node->key, node->val);
    }
    insertIncNodeToHashTable(incNode);

    return ans;
}

LFUCache 的 put 实现

void put(int key, int value) {
    if (!key2LRUNode.count(key)) {
        if (size_ >= capacity_) {
            IncNode* incNode = head->next;
            LRUNode* lruNode = incNode->lru->pop();
            removeLRUNodeFromHashTable(lruNode);
            size_ -= 1;
        }
        LRUNode* node = new LRUNode(key, value);
        // 如果新的 lru 不存在,就创建

        IncNode* incNode;
        // 不存在计数为 1 的 IncNode,创建
        if (!cnt2IncNode.count(node->cnt)) {
            incNode = new IncNode(node->cnt);
            incNode->lru->put(node->key, node->val);
            // 将这个 IncNode 添加到 IncNode 对应的链表中
            insertIncNodeFromLinklist(incNode, head);
        } else {
            incNode = cnt2IncNode[node->cnt];
            incNode->lru->put(node->key, node->val);
        }
        insertIncNodeToHashTable(incNode);
        insertLRUNodeToHashTable(node);
        size_ += 1;
    } else {
        LRUNode* node = key2LRUNode[key];
        node->val = value;

        IncNode* cur = cnt2IncNode[node->cnt];
        IncNode* pre = cur->prev;

        LRUCache* lru = cur->lru;
        // 从当前 lru 中删除
        lru->del(node->key);
        // 如果 lru 为空,说明要合并了,先找到前后的两个
        if (lru->empty()) {
            removeIncNodeFromHashTable(cur);
            removeIncNodeFromLinklist(cur);
        } else {
            pre = cur;
        }

        IncNode* incNode;
        // 添加到新的 lru 中
        node->cnt += 1;
        // 如果新的 lru 不存在,就创建
        if (!cnt2IncNode.count(node->cnt)) {
            incNode = new IncNode(node->cnt);
            incNode->lru->put(node->key, node->val);
            insertIncNodeFromLinklist(incNode, pre);
            cnt2IncNode[node->cnt] = incNode;
        } else {
            incNode = cnt2IncNode[node->cnt];
            incNode->lru->put(node->key, node->val);
        }
        insertIncNodeToHashTable(incNode);
        insertLRUNodeToHashTable(node);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LFU的实现 的相关文章

  • 【每日一题】ABC194E-Mex Min

    题目内容 原题链接 给定一个长度为 n n n 的整数数组 a a a 求所有长度为 m m
  • PTA 剥洋葱(C语言 + 详细注释 + 代码超简单)

    输入格式 一行 一个整数 即图形的层数 输出格式 如上述图形 输入样例 3 输出样例 AAAAA ABBBA ABCBA ABBBA AAAAA 打印图形题关键是找规律 一般只需两重循环 行循环 列循环 include
  • 【每日一题】ARC137B - Count 1’s

    题目内容 原题链接 给定一个长度为 n n n 的整数数组 a a a a i
  • 146. LRU Cache

    1 The key to solve this problem is using a double linked list which enables us to quickly move nodes 2 The LRU cache is
  • ◆考试题目◆◇NOIP模拟赛◇turtle(乌龟)

    NOIP模拟赛 turtle Description 一只乌龟由于智商低下 它只会向左或向右走 不过它会遵循主人小h的指令 F 向前走一步 T 掉头 现在小h给出一串指令 由于小h有高超的计算能力 他可以马上知道乌龟最后走到哪里 为了难倒小
  • 蓝桥杯第十届(2019)B组省赛1-9题练手源码

    1 组队 枚举 题目 作为篮球队教练 你需要从以下名单中选出 1 号位至 5 号位各一名球员 组成球队的首发阵容 每位球员担任 1 号位至 5 号位时的评分如下表所示 请你计算首发阵容 1 号位至 5 号位的评分之和最大可能是多少 题解 过
  • 关于 Python 之 Matplotlib 的总结

    文章目录 通用 简单例子 中文显示问题 参数 颜色 color参数表 线型 linestyle ls 参数表 标记符号 marker参数表 位置 legend loc参数表 plt 常用命令 图形模板 柱形图 饼图 散点图 直方图 箱线图
  • LRU缓存机制

    LRU缓存机制LeetCode146官方题解 struct DLinkedNode int key value DLinkedNode prev DLinkedNode next DLinkedNode key 0 value 0 prev
  • LRU的实现

    题目内容 实现一个 LRUCache 类 三个接口 LRUCache int capacity 创建一个大小为 capacity 的缓存 get int key 从缓存中获取键为 key 的键值对的 value put int key in
  • 【LeetCode训练营 189】轮转数组详解

    博客内容 LeetCode训练营 189 轮转数组详解 作 者 陈大大陈 个人简介 一个正在努力学技术的准前端 专注基础和实战分享 欢迎私信 欢迎大家 这里是CSDN 我总结知识和写笔记的地方 喜欢的话请三连 有问题请私信 目录 暴力法 辅
  • 初步认识Ehcache清空缓存的3种策略

    Ehcache是一种广泛使用的开源Java分布式缓存 主要面向通用缓存 Java EE和轻量级容器 它具有内存和磁盘存储 缓存加载器 缓存扩展 缓存异常处理程序 一个gzip缓存servlet过滤器 支持REST和SOAP api等特点 在
  • 蓝桥杯决赛之行的感悟

    这是一篇瞎写的感悟 想到哪写到哪 从去年11月份开始参加学校的训练团队 到现在5月份 也是半年多一个月了 期间就参加了比较水的蓝桥杯 另外还有就是百度之星 蓝桥杯确实跟大伙说的差不多 含金量并不高 适合大众玩的 不适合黑客玩 省赛的话 一共
  • 【算法竞赛】Python快速入门指南

    该指南由GPT4编写 用于快速入门蓝桥杯Python组 当然 仅限入门而已 本指南由GPT 4生成 我只是负责引导 并对内容进行整理和补充 一直以来我都是使用C 作为算法竞赛语言 但是奈何C 组太卷 自己又太菜 于是另谋他路 Prompt模
  • LRU缓存设计

    最近最少使用 LRU 缓存是先丢弃最近最少使用的项 如何设计和实现这样一个缓存类 设计要求如下 1 尽快找到该项目 2 一旦缓存未命中并且缓存已满 我们需要尽快替换最近最少使用的项 如何从设计模式和算法设计角度来分析和实现这个问题 链表 指
  • 清除Python中所有lru_cache

    我在 python 中有一些带有 lru cache 缓存的函数 例如 lru cache maxsize None def my function 虽然我可以单独清除缓存 例如my function cache clear 有没有办法一次
  • IDictionary 是否有 LRU 实现?

    我想实现一个简单的内存中 LRU 缓存系统 并且我正在考虑一个基于 IDictionary 实现的解决方案 该解决方案可以处理散列 LRU 机制 来自java 我有以下经验LinkedHashMap 它可以很好地满足我的需要 我在任何地方都
  • Android LruCache(Android 3.1)线程安全

    是新的Android类LruCache http developer android com reference android util LruCache html线程安全 java 文档说 这个类是线程安全的 通过在缓存上同步以原子方式
  • 如何防止LRU缓存android中的内存不足错误

    我在我的 Android 应用程序中使用内存 LRU 缓存来缓存位图 但是在将某些位图加载到 LRU 映射中后 应用程序强制关闭并提示内存不足异常 我花了一整天的时间 但还没有找到解决方案 请任何人都可以帮助我 我严重陷入这个问题 提前致谢
  • 生产代码中的 LRU 实现

    我有一些 C 代码 需要使用 LRU 技术实现缓存替换 目前我知道两种实现LRU缓存替换的方法 每次访问缓存数据时使用时间戳 最后比较替换时的时间戳 使用缓存项的堆栈 如果最近访问过它们 则将它们移动到顶部 因此最后底部将包含 LRU 候选
  • 使@lru_cache忽略一些函数参数

    我怎样才能使 functools lru cache https docs python org 3 library functools html functools lru cache装饰器忽略一些与缓存键有关的函数参数 例如 我有一个如

随机推荐

  • 测试相关知识点

    设计测试用例 主要从功能性 性能性 安全性 易用性 兼容性 网络测试这几个方面来设计 需要考虑问题的角度全面 注意如果是手机端app测试的话需要加上中断测试这项 考虑后台切换 app切换 拔插数据线 来短信 电话 其他app消息 1 微信朋
  • openMP + cuda 实现多GPU编程

    include
  • @FeignClient configuration参数配置

    1 我们定义Feign Client时候 可以通过configuration参数指定一个配置类 那么指定的这个配置入口类上面是否需要添加 Configuration 注解呢 FeignClient name OrderServiceClie
  • 渗透测试工具Burp Suite详解

    Burp Suite 的安装 Burp Suite是一款集成化的渗透测试工具 包含了很多功能 可以帮助我们高效地完成对Web应用程序的渗透测试和攻击 Burp Suite由Java语言编写 基于Java自身的跨平台性 使这款软件学习和使用起
  • ffmpeg 和 opencv 编译

    一 ffmpeg编译 ffmpeg 编译参数 configure enable gpl disable x86asm enable shared enable pic enable static 二 opencv编译 1 安装依赖库 sud
  • 两个栈实现队列 和 两个队列实现栈

    1 两个栈实现队列 核心 push操作 每次总是往stack1 push元素 pop操作 每次总是从stack2 pop元素 分stack2是否empty分为两种情况 static final Stack
  • FEC介绍(四)—RS(544,514)编解码过程【转载】

    https zhuanlan zhihu com p 103888948 utm source wechat session
  • JAVA基础知识点总结

    文章目录 前言 一 JAVA简介 二 基础语法 面向对象 String Integer Object 异常 IO 序列化 Java 泛型 注解 反射 前言 一 JAVA简介 Java 是一门面向对象的编程语言 语言特点 面向对象 平台无关性
  • ES6 flat 与数组扁平化

    前言 flat 用于将多维数组拉平 扁平化 不影响原数组 返回新的数组 1 2 3 4 flat 1 2 3 4 仅有一个参数depth 用于指定拉平的深度 默认值为1 若depth指定为非正数 将返回原数组 指定为Infinity 无论多
  • 线程间发布和订阅

    include
  • 刷脸支付可以自动识别会员可以领券打折

    刷脸支付说白了就是用自己的脸 身份证明 来跟金融做的一个消费交易 大家对于信息这个事情是非常敏感的 因此就会存在一个安全风险问题 还有就是对商家泄露的信息太多 造成消费者的担心等情况 也是时有发生 靠脸吃饭之前只是一句调侃 如今却成为了现实
  • 01 Java NIO NIO和IO的区别

    Java NIO NIO和IO的区别 NIO和IO的区别 面向流与面向缓冲 阻塞与非阻塞IO 选择器 Selectors NIO和IO如何影响应用程序的设计 API调用 数据处理 设置处理线程数 Java IO流专栏中主要介绍了java i
  • vue3之toRefs

    把一个响应式对象转换成普通对象 该普通对象的每个属性都是一个ref reactive的响应式功能赋予给对象的 给对象结构或展开的时候 会让数据丢失响应式能力 使用toRefs可以保证该对象展开的每一个属性都是响应式的 案例一
  • 挖到过src吗?请描述一下过程

    挖到过src吗 请描述一下过程 SRC 安全漏洞奖励计划 是一种由企业或组织设立的计划 旨在鼓励独立的安全研究人员发现并报告其系统或应用程序中的漏洞 这些计划的推出是为了提高安全性 及时修复潜在的漏洞 并奖励那些贡献漏洞发现的研究人员 SR
  • 如何让网页变灰色

    在一些重大节日 如何快速使网站网页变成灰色 黑白色 在网页的标签内加入以下代码 如果想让单个网页变灰色 就写在单网页里面 如果写在继承的网页里面 是整体的变灰色 如果你不想改动CSS文件 你可以通过在网页头部中的标签内部加入内联CSS代码的
  • c语言数学追赶法编程,计算方法——C语言实现——追赶法求解非线性方程

    最近在上计算方法这门课 要求是用MATLAB做练习题 但是我觉得C语言也很棒棒啊 题目 一般三对角线性方程组的求解用这个方法 三对角线性方程组也称为带状矩阵 这方法基础上还是LU分解法 只是比LU分解法计算方法上简单一些 使用VS2017
  • [HCTF 2018]admin 1 弱口令和爆破解法

    HCTF 2018 admin 继续buu刷题 几天刷到一道比较有意思的题 HCTF 2018 admin 打开环境之后 右上角 点击login 既然题目名字都提示了admin 猜测就是弱口令 admin加123 试一下 直接就登录进去了
  • pytest自动化测试框架基础篇

    目录 前言 一 单元测试框架 二 pytest简介以及常用插件安装 三 pytest默认测试用例的规则以及基础应用 四 pytest跳过测试用例 五 pytest测试用例的前后置 固件 前言 pytest是一个基于Python语言的自动化测
  • C++ 11 新容器和新算法

    目录 新容器 forward list Abstract How Demo array Abstract Comparewith vector Compare with original array How Demo tuple Abstr
  • LFU的实现

    题目内容 实现一个 LFUCache 类 三个接口 LFUCache int capacity 创建一个大小为 capacity 的缓存 get int key 从缓存中获取键为 key 的键值对的 value put int key in