每日两题 T16
算法
LeetCode T460. LFU缓存[1]
描述
设计并实现最不经常使用(LFU)
缓存的数据结构。它应该支持以下操作:get
和 put
。
get(key)
- 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。 put(key, value)
- 如果键不存在,请设置或插入值。当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除。
进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?
示例
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
分析
我们先回顾一下常用的缓存算法
LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。 LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。 FIFO (Fist in first out) 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉。
双hash
一个存储数据,给定的 key
作为键,给定的 value
、freq
组成对象作为值;一个存储使用频率 freq
作为键,符合该频率的 key
组成数组作为值。
put
操作时,我们检测该值是否存在于 cache
中,若不存在则插入,并更新 freq
,若存在,则直接更新 cache
、freq
。
get
操作获取值的同时,将该freq
中该 key
频率数组+1即可。
代码
/**
* @param {number} capacity
*/
var LFUCache = function (capacity) {
this.limit = capacity
this.cache = new Map()
this.freqMap = new Map()
};
/**
* @param {number} key
* @return {number}
*/
LFUCache.prototype.get = function (key) {
let cache = this.cache
let r = -1
if (typeof cache[key] != "undefined") {
let o = cache[key]
r = o.value
//更新频率记录
this.updateL(key, o)
}
return r
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LFUCache.prototype.put = function (key, value) {
let { cache, limit, freqMap: fmap } = this
if (limit <= 0) return
if (typeof key == "undefined" || typeof value == "undefined") throw new Error('key or value is undefined')
// 存在则直接更新
if (typeof cache[key] == "undefined") {
// 获取频率 key
// 判断容量是否满
if (Object.keys(cache).length == limit) {
let fkeys = Object.keys(fmap)
let freq = fkeys[0]
// 获取key集合
let keys = fmap[freq]
// 淘汰首位
delete cache[keys.shift()]
// delete cache[keys[0]];
// keys.splice(0, 1);
// 清理
if (fmap[freq].length == 0) delete fmap[freq]
}
// 频率记录是否存在
if (!fmap[1]) fmap[1] = []
// 插入新值
fmap[1].push(key)
cache[key] = {
value: value,
freq: 1 // 默认的频率
}
} else {
cache[key].value = value
//更新频率记录
this.updateL(key, cache[key])
}
};
LFUCache.prototype.updateL = function (key, obj) {
let { freq } = obj;
let arr = this.freqMap[freq]
// 删除原频率记录
this.freqMap[freq].splice(arr.indexOf(key), 1)
// 清理
if (this.freqMap[freq].length == 0) delete this.freqMap[freq]
// 更新频率
freq = obj.freq = obj.freq + 1
if (!this.freqMap[freq]) this.freqMap[freq] = []
this.freqMap[freq].push(key)
}
/**
* Your LFUCache object will be instantiated and called as such:
* var obj = new LFUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/
JavaScript
箭头函数与普通函数(function)的区别
引入箭头函数有两个方面的作用:更简短的函数并且不绑定this。箭头函数与普通函数不同之处有:
1.箭头函数没有 this,它会从自己的作用域链的上一层继承 this(因此无法使用 apply / call / bind 进行绑定 this 值);2.不绑定 arguments,当在箭头函数中调用 aruguments 时同样会向作用域链中查询结果;3.不绑定 super 和 new.target;4.没有 prototype 属性,即指向 undefined;5.无法使用 new 实例化对象,因为普通构造函数通过 new 实例化对象时 this 指向实例对象,而箭头函数没有 this 值,同时箭头函数也没有 prototype。
另外提一点,在使用 React
、Vue
相关框架时,要注意,生命周期函数使用箭头函数会带来一些问题。
References
[1]
460. LFU缓存: https://leetcode-cn.com/problems/lfu-cache/
- 轻松初探 Python 篇(五)—dict 和 set 知识汇总
- 全面解析C#中的异步编程为什么要异步过去糟糕的体验一个新的方式Tasks基于任务的异步编程模型Async和await时间处理程序和无返回值的异步方法结束语
- CSS深入理解学习笔记之absolute
- 5个经典的JavaScript面试题
- 轻松初探 Python 篇(四)—list tuple range 知识汇总
- CSS深入理解学习笔记之overflow
- Python爬虫实践——简单爬取我的博客
- Python爬虫入门(二)
- 在ASP.NET MVC5应用程序中快速接入QQ和新浪微博OAuth起步创建应用程序使用NUGET更新OWIN中间件启动SSL支持申请腾讯QQ的Oauth申请新浪微博的Oauth快速接入资源地址&源码
- 有趣的算法(六) ——Find-Union算法
- 有趣的算法(七) ——快速排序改进算法
- 编写你人生中第一个机器学习代码吧!
- 使用Octave来学习Machine Learning(二)
- RESTful API的十个最佳实践1. 使用名词而不是动词 2. Get方法和查询参数不应该改变资源状态3. 使用名词的复数形式 4. 为关系使用子资源 5. 使用HTTP头决定序列化格式 6. 使
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis 哨兵机制以及底层原理深入解析,这次终于搞清楚了
- SQL 找出分组中具有极值的行
- 接入层Nginx架构及模块介绍分享
- 【问题修复】mds0: Metadata damage detected
- 【服务网格架构】Envoy架构概览(6):异常检测
- 分布式存储Cephfs读取优化方案
- SQL 确定序列里缺失值的范围
- 【问题修复】osd自杀问题跟踪
- mds元信息缓存不释放问题
- 线程安全问题,synchronized 和 ReentrantLock 详细讲解
- Ceph RBD灾备方案对比
- RBD快照灾备方案
- 从条件运算符说起,反思什么是好代码
- Ceph调试开发环境搭建
- Ceph MDS问题分析