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
  • LeetCode 460. LFU Cache

    原题网址 https leetcode com problems lfu cache Design and implement a data structure for Least Frequently Used LFU cache It
  • 【每日一题】ABC194E-Mex Min

    题目内容 原题链接 给定一个长度为 n n n 的整数数组 a a a 求所有长度为 m m
  • C++ vector、string使用

    vector就是类似于一个数组的容器 内容比数组更加全面 很多操作都有自己的函数可以直接拿过来进行使用 主要函数就是 v push back k 尾插元素 v insert it k 在任意位置插入元素 v eraser it it k 删
  • 关于 Python 之 Matplotlib 的总结

    文章目录 通用 简单例子 中文显示问题 参数 颜色 color参数表 线型 linestyle ls 参数表 标记符号 marker参数表 位置 legend loc参数表 plt 常用命令 图形模板 柱形图 饼图 散点图 直方图 箱线图
  • 洛谷P1149(NOIP2008) 火柴棒等式 (C语言 + 详细注释)

    题目描述 给你n根火柴棍 你可以拼出多少个形如 A B C 的等式 等式中的A B C是用火柴棍拼出的整数 若该数非零 则最高位不能是00 用火柴棍拼数字0 9的拼法如图所示 注意 加号与等号各自需要两根火柴棍 如果A B 则A B C与B
  • 【每日一题】补档 ABC309F - Box in Box

    题目内容 原题链接 给定 n n n 个箱子 问是否存在一个箱子 x x x 是否可以放到另一个箱子 y
  • 【LeetCode训练营 189】轮转数组详解

    博客内容 LeetCode训练营 189 轮转数组详解 作 者 陈大大陈 个人简介 一个正在努力学技术的准前端 专注基础和实战分享 欢迎私信 欢迎大家 这里是CSDN 我总结知识和写笔记的地方 喜欢的话请三连 有问题请私信 目录 暴力法 辅
  • 蓝桥杯决赛之行的感悟

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

    一 LRU算法介绍 LRU算法全称Least Recently Used 也就是检查最近最少使用的数据的算法 这个算法通常使用在内存淘汰策略中 用于将不常用的数据转移出内存 将空间腾给最近更常用的 热点数据 算法很简单 只需要将所有数据按使
  • 【算法】欧拉函数公式证明

    定义 欧拉函数 n varphi n n 表示小于等于 n n n且与
  • 缓存淘汰算法 —— LFU-Aging(Java实现)

    Java实现 用HashMap保存关系 key值 命中次数与上次命中时间 当需要淘汰某个key值时 调用map remove key import java util public class LFUAgingMap
  • 扫雷游戏是一款十分经典的单机小游戏。 问题 H: 扫雷游戏

    题目描述 扫雷游戏是一款十分经典的单机小游戏 在n行m列的雷区中有一些格子含有地雷 称之为地雷格 其他格子不含地雷 称之为非地雷格 玩家翻开一个非地雷格时 该格将会出现一个数字 提示周围格子中有多少个是地雷格 游戏的目标是在不翻出任何地雷格
  • 【算法竞赛】Python快速入门指南

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

    我正在为面试做准备 想重温一下我对缓存的记忆 如果CPU有一个带有LRU替换策略的缓存 那么它在芯片上实际上是如何实现的呢 每个缓存行会存储一个时间戳记吗 另外 在双核系统中两个 CPU 同时写入同一个地址时会发生什么情况 对于只有两种路的
  • 清除Python中所有lru_cache

    我在 python 中有一些带有 lru cache 缓存的函数 例如 lru cache maxsize None def my function 虽然我可以单独清除缓存 例如my function cache clear 有没有办法一次
  • Python:构建 LRU 缓存

    我身边有6 00 000 entries in MongoDB采用以下格式 feature category count where feature可以是任何词 category为正或负 并且 count告诉某个功能在该类别的文档中出现了多
  • 记住一个函数,以便当我在 Python 中重新运行该文件时它不会被重置

    我经常在 Python 中进行交互式工作 其中涉及一些我不想经常重复的昂贵操作 我通常运行我经常处理的任何 Python 文件 如果我写 import functools32 functools32 lru cache def square
  • Android LruCache(Android 3.1)线程安全

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

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

随机推荐

  • 测试相关知识点

    设计测试用例 主要从功能性 性能性 安全性 易用性 兼容性 网络测试这几个方面来设计 需要考虑问题的角度全面 注意如果是手机端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