[LeetCode] LFU Cache 最近最不常用页面置换缓存器
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ ); cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
这道题是让我们实现最近不常用页面置换算法LFU (Least Frequently Used), 之前我们做过一道类似的题LRU Cache,让我们求最近最少使用页面置换算法LRU (Least Recnetly Used)。两种算法虽然名字看起来很相似,但是其实是不同的。顾名思义,LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面。光说无凭,举个例子来看看,比如说我们的cache的大小为3,然后我们按顺序存入 5,4,5,4,5,7,这时候cache刚好被装满了,因为put进去之前存在的数不会占用额外地方。那么此时我们想再put进去一个8,如果使用LRU算法,应该将4删除,因为4最久未被使用,而如果使用LFU算法,则应该删除7,因为7被使用的次数最少,只使用了一次。相信这个简单的例子可以大概说明二者的区别。
这道题比之前那道LRU的题目还要麻烦一些,因为那道题只要用个list把数字按时间顺序存入,链表底部的位置总是最久未被使用的,每次删除底部的值即可。而这道题不一样,由于需要删除最少次数的数字,那么我们必须要统计每一个key出现的次数,所以我们用一个哈希表m来记录当前数据{key, value}和其出现次数之间的映射,这样还不够,为了方便操作,我们需要把相同频率的key都放到一个list中,那么需要另一个哈希表freq来建立频率和一个里面所有key都是当前频率的list之间的映射。由于题目中要我们在O(1)的时间内完成操作了,为了快速的定位freq中key的位置,我们再用一个哈希表iter来建立key和freq中key的位置之间的映射。最后当然我们还需要两个变量cap和minFreq,分别来保存cache的大小,和当前最小的频率。
为了更好的讲解思路,我们还是用例子来说明吧,我们假设cache的大小为2,假设我们已经按顺序put进去5,4,那么来看一下内部的数据是怎么保存的,由于value的值并不是很重要,为了不影响key和frequence,我们采用value#来标记:
m:
5 -> {value5, 1}
4 -> {value4, 1}
freq:
1 -> {5,4}
iter:
4 -> list.begin() + 1
5 -> list.begin()
这应该不是很难理解,m中5对应的频率为1,4对应的频率为1,然后freq中频率为1的有4和5。iter中是key所在freq中对应链表中的位置的iterator。然后我们的下一步操作是get(5),下面是get需要做的步骤:
1. 如果m中不存在5,那么返回-1
2. 从freq中频率为1的list中将5删除
3. 将m中5对应的frequence值自增1
4. 将5保存到freq中频率为2的list的末尾
5. 在iter中保存5在freq中频率为2的list中的位置
6. 如果freq中频率为minFreq的list为空,minFreq自增1
7. 返回m中5对应的value值
经过这些步骤后,我们再来看下此时内部数据的值:
m:
5 -> {value5, 2}
4 -> {value4, 1}
freq:
1 -> {4}
2 -> {5}
iter:
4 -> list.begin()
5 -> list.begin()
这应该不是很难理解,m中5对应的频率为2,4对应的频率为1,然后freq中频率为1的只有4,频率为2的只有5。iter中是key所在freq中对应链表中的位置的iterator。然后我们下一步操作是要put进去一个7,下面是put需要做的步骤:
1. 如果调用get(7)返回的结果不是-1,那么在将m中7对应的value更新为当前value,并返回
2. 如果此时m的大小大于了cap,即超过了cache的容量,则:
a)在m中移除minFreq对应的list的首元素的纪录,即移除4 -> {value4, 1}
b)在iter中清除4对应的纪录,即移除4 -> list.begin()
c)在freq中移除minFreq对应的list的首元素,即移除4
3. 在m中建立7的映射,即 7 -> {value7, 1}
4. 在freq中频率为1的list末尾加上7
5. 在iter中保存7在freq中频率为1的list中的位置
6. minFreq重置为1
经过这些步骤后,我们再来看下此时内部数据的值:
m:
5 -> {value5, 2}
7 -> {value7, 1}
freq:
1 -> {7}
2 -> {5}
iter:
7 -> list.begin()
5 -> list.begin()
参见代码如下:
class LFUCache {
public:
LFUCache(int capacity) {
cap = capacity;
}
int get(int key) {
if (m.count(key) == ) return -;
freq[m[key].second].erase(iter[key]);
++m[key].second;
freq[m[key].second].push_back(key);
iter[key] = --freq[m[key].second].end();
if (freq[minFreq].size() == ) ++minFreq;
return m[key].first;
}
void put(int key, int value) {
if (cap <= ) return;
if (get(key) != -) {
m[key].first = value;
return;
}
if (m.size() >= cap) {
m.erase(freq[minFreq].front());
iter.erase(freq[minFreq].front());
freq[minFreq].pop_front();
}
m[key] = {value, };
freq[].push_back(key);
iter[key] = --freq[].end();
minFreq = ;
}
private:
int cap, minFreq;
unordered_map<int, pair<int, int>> m;
unordered_map<int, list<int>> freq;
unordered_map<int, list<int>::iterator> iter;
};
类似题目:
参考资料:
https://leetcode.com/problems/lfu-cache/
https://discuss.leetcode.com/topic/69436/concise-c-o-1-solution-using-3-hash-maps-with-explanation
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] LFU Cache 最近最不常用页面置换缓存器的更多相关文章
- [LeetCode] 460. LFU Cache 最近最不常用页面置换缓存器
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the f ...
- [LeetCode] LRU Cache 最近最少使用页面置换缓存器
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- [LeetCode] 146. LRU Cache 最近最少使用页面置换缓存器
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- Leetcode: LFU Cache && Summary of various Sets: HashSet, TreeSet, LinkedHashSet
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the f ...
- LeetCode LFU Cache
原题链接在这里:https://leetcode.com/problems/lfu-cache/?tab=Description 题目: Design and implement a data str ...
- (待续)C#语言中的动态数组(ArrayList)模拟常用页面置换算法(FIFO、LRU、Optimal)
目录 00 简介 01 算法概述 02 公用方法与变量解释 03 先进先出置换算法(FIFO) 04 最近最久未使用(LRU)算法 05 最佳置换算法(OPT) 00 简介 页面置换算法主要是记录内存 ...
- leetcode 146. LRU Cache 、460. LFU Cache
LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面,如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目. LFU ...
- 页面置换算法 - FIFO、LFU、LRU
缓存算法(页面置换算法)-FIFO. LFU. LRU 在前一篇文章中通过leetcode的一道题目了解了LRU算法的具体设计思路,下面继续来探讨一下另外两种常见的Cache算法:FIFO. LFU ...
- 缓存算法(页面置换算法)-FIFO、LFU、LRU
在前一篇文章中通过leetcode的一道题目了解了LRU算法的具体设计思路,下面继续来探讨一下另外两种常见的Cache算法:FIFO.LFU 1.FIFO算法 FIFO(First in First ...
随机推荐
- Eclipse 安装 SVN 的在线插件
这是继上次svn 客户端与服务器安装后的如何在Eclipse 环境下在线安装 SVN插件,我的Eclipse版本是4.50 SVN的在线安装 下面为大家提供SVN 的在线安装教程.下面是安装的 详细过 ...
- kafka性能参数和压力测试揭秘
转自:http://blog.csdn.net/stark_summer/article/details/50203133 上一篇文章介绍了Kafka在设计上是如何来保证高时效.大吞吐量的,主要的内容 ...
- c#面向对象基础技能——学习笔记(五)委托技术在开发中的应用
委托 delegate 1.是一种全新的面向对象语言的特性: 2.开发事件驱动程序变得非常简单: 3.简化多线程难度. 理解委托:可以理解成一个方法的指针.(接收的变量是方法) 步骤: 1.声明委托, ...
- C#定时执行
代码: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; ...
- 基于Metronic的Bootstrap开发框架经验总结(13)--页面链接收藏夹功能的实现2(利用Sortable进行拖动排序)
在上篇随笔<基于Metronic的Bootstrap开发框架经验总结(12)--页面链接收藏夹功能的实现>上,我介绍了链接收藏夹功能的实现,以及对收藏记录的排序处理.该篇随笔主要使用功能按 ...
- DOM加载过程中ready和load的区别
在浏览器地址栏输入URL地址,浏览器开始加载页面时,有以下几个过程 1.浏览器开始解析HTML文档 2. 浏览器遇到HTML文档中的<script>元素以及CSS样式文件,并且没有asyn ...
- 压缩html
最近研究程序的优化,压缩html可以减小html的体积,有利于提高页面的相应速度.在webform的basepage中添加如下代码: /// <summary> /// 重写Render方 ...
- Android开发aidl使用中linkToDeath和unlinkToDeath的使用
1.Binder死亡代理 这一节首先将介绍Binder类中比较重要的两个方法linkToDeath和unlinkToDeath.我们知道Binder是运行在服务进程,若服务端进程因为某种原因“ ...
- iOS获取app图标和启动图片名字(AppIcon and LaunchImage's name)
在某种场景下,可能我们需要获取app的图标名称和启动图片的名称.比如说app在前台时,收到了远程通知但是通知栏是不会有通知提醒的,这时我想做个模拟通知提示,需要用到icon名称:再比如在加载某个控制器 ...
- IO流总结
IO流的作用:用于设备和设备之间的数据传输. IO流的概念:流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象. IO流的分类: 按照操作数据的类型分为两种: 字节流和字符流. 按 ...