每日两题 T16

时间:2022-07-22
本文章向大家介绍每日两题 T16,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

算法

LeetCode T460. LFU缓存[1]

描述

设计并实现最不经常使用(LFU)缓存的数据结构。它应该支持以下操作:getput

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 作为键,给定的 valuefreq组成对象作为值;一个存储使用频率 freq 作为键,符合该频率的 key 组成数组作为值。

put 操作时,我们检测该值是否存在于 cache 中,若不存在则插入,并更新 freq,若存在,则直接更新 cachefreq

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。

另外提一点,在使用 ReactVue相关框架时,要注意,生命周期函数使用箭头函数会带来一些问题。

References

[1] 460. LFU缓存: https://leetcode-cn.com/problems/lfu-cache/