LeetCode - 460 LFU缓存(Java JS Python)

题目来源

460. LFU 缓存 - 力扣(LeetCode)

题目描述

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。
  • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最久未使用 的键。

为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作,使用计数器的值将会递增。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

示例

输入

["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]

输出

[null, null, null, 1, null, -1, 3, null, -1, 3, 4]

解释

// cnt(x) = 键 x 的使用计数
// cache=[] 将显示最后一次使用的顺序(最左边的元素是最近的)
LFUCache lfu = new LFUCache(2);
lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
lfu.get(1);      // 返回 1// cache=[1,2], cnt(2)=1, cnt(1)=2
lfu.put(3, 3);   // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小// cache=[3,1], cnt(3)=1, cnt(1)=2
lfu.get(2);      // 返回 -1(未找到)
lfu.get(3);      // 返回 3// cache=[3,1], cnt(3)=2, cnt(1)=2
lfu.put(4, 4);   // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用// cache=[4,3], cnt(4)=1, cnt(3)=2
lfu.get(1);      // 返回 -1(未找到)
lfu.get(3);      // 返回 3// cache=[3,4], cnt(4)=1, cnt(3)=3
lfu.get(4);      // 返回 4// cache=[3,4], cnt(4)=2, cnt(3)=3

提示

  • 1 <= capacity <= 104
  • 0 <= key <= 105
  • 0 <= value <= 109
  • 最多调用 2 * 10^5 次 get 和 put 方法

题目解析

LFU缓存可以通过:两个哈希表(Map结构) + 双向链表来实现。

我们可以定义两个哈希表keyMap和freqMap,其中:

  • keyMap的键是"本题key",值为"双向链表节点node"
  • freqMap的键是"本题key的使用次数freq",值为”对应使用次数freq的key对应的node组成的双向链表“

而双向链表节点node用于记录key的如下信息:

  • key:本题的key
  • val:本题的value
  • freq:对应key使用次数

可能上面说法比较晦涩,下面通过图示来说:










以上就是两个哈希Map,以及双向链表完成LFU缓存的逻辑。

上面逻辑中,有一个问题,那么就是每次LFU容量不足时,我们需要删除掉最少、最远使用的key,那么首先如何找到最少使用次数的key呢?

上面图示中,我们是通过人眼识别出freqMap键列中最小的键,即为最少使用次数。

那么代码该如何实现呢?

我们可以定义一个全局变量(或者类静态变量)minFreq来记录最少使用次数,而minFreq的更新有如下时机:

  • 1、put新增操作一定会带来一个使用次数freq=1的键,且此新键的使用次数1一定是最少的,此时我们可以更新minFreq=1
  • 2、get和put更新操作会新增对应key的使用次数,因此在新增使用次数后,该key需要从对应使用次数的DLink中去除,如果对应DLink只有该key对应节点,且对应DLink是最少使用次数对应的容器链表,那么删除key后,该DLInk就为空,那么最少使用次数的key就没了。此时我们可以直接将minFreq++,因为如果当前key在新增使用次数前是唯一的最少使用次数key,那么当前key新增使用次数后,依旧是最少使用次数的key。而minFreq就是当前key新增使用次数前的使用次数。

Java算法源码

import java.util.HashMap;class LFUCache {/** 双向链表节点 */static class Node {/** 记录本题的键 */int key;/** 记录本题的值 */int val;/** 记录该键被访问的次数 */int freq;/** 当前节点的上一个节点 */Node prev;/** 当前节点的下一个节点 */Node next;public Node(int key, int val, int freq) {this.key = key;this.val = val;this.freq = freq;this.prev = null;this.next = null;}}/** 双向链表 */static class Link {/** 链表中节点个数 */int size;/** 链表头节点 */Node head;/** 链表尾节点 */Node tail;public Link() {this.size = 0;this.head = null;this.tail = null;}/*** 尾插** @param node 要被插入的节点*/public void addLast(Node node) {if (this.size == 0) {// 空链表,则node节点插入后,即为头、尾节点this.head = node;this.tail = node;} else {// 非空链表,则node节点插入到tail节点后面this.tail.next = node;node.prev = this.tail;this.tail = node;}this.size++;}/*** 删除指定节点** @param node 要删除的节点*/public void remove(Node node) {// 空链表没有节点,所以无法删除if (this.size == 0) return;if (this.size == 1) {// 链表只有一个节点,则删除完后,变为空链表this.head = null;this.tail = null;} else if (this.head == node) {// 如果要删除的节点是头节点this.head = this.head.next;this.head.prev = null;} else if (this.tail == node) {// 如果要删除的节点是尾节点this.tail = this.tail.prev;this.tail.next = null;} else {// 如果要删除的节点是中间节点node.prev.next = node.next;node.next.prev = node.prev;}this.size--;}}/** keyMap用于记录key对应的node */HashMap<Integer, Node> keyMap;/** freqMap的key是访问次数,value是具有相同访问次数的key对应的node组成的链表,链表头是最远访问的,链表尾是最近访问的 */HashMap<Integer, Link> freqMap;/** LFU缓存中能记录的最多key的数量 */int capacity;/** LFU缓存中所有的key中最少的访问次数 */int minFreq;public LFUCache(int capacity) {this.keyMap = new HashMap<>();this.freqMap = new HashMap<>();this.capacity = capacity;this.minFreq = 0;}public int get(int key) {if (this.keyMap.containsKey(key)) {// 存在对应key,则返回对应valNode node = this.keyMap.get(key);incNodeFreq(node); // get操作会新增对应key的访问次数return node.val;} else {// 不存在对应key,则返回-1return -1;}}public void put(int key, int value) {// 对应key已存在,则为更新场景if (this.keyMap.containsKey(key)) {Node node = this.keyMap.get(key);incNodeFreq(node); // 更新操作会增加对应key的访问次数node.val = value;}// 对应key不存在,则为新增场景else {// 先判断容量是否超过,keyMap的key数量就是LFU缓存中记录的key数量if (this.keyMap.size() >= this.capacity) {Link link = this.freqMap.get(this.minFreq);int removeKey = link.head.key;link.remove(link.head); // 最少访问次数所在链表的头节点,即为:最少、最远访问的key,容量不足时,需要优先删除它this.keyMap.remove(removeKey); // 注意,不要遗漏将keyMap中该key删除}// 新增key,则对应key的访问次数为1,且为最少访问次数this.minFreq = 1;Node node = new Node(key, value, this.minFreq);// 将新增key对应的node加入到freqMap,和keyMap中this.freqMap.putIfAbsent(this.minFreq, new Link());this.freqMap.get(this.minFreq).addLast(node);this.keyMap.put(key, node);}}/*** 增加key的访问次数** @param node key对应的node*/public void incNodeFreq(Node node) {// 由于key的访问次数增加,因此要从原访问次数的链表中删除this.freqMap.get(node.freq).remove(node);// 如果原链表删除当前key对应的节点后为空,且原链表对应的访问次数就是最少访问次数if (this.freqMap.get(node.freq).size == 0 && node.freq == this.minFreq) {// 则最少访问次数对应的key没有了,因此最少访问次数++(即当前key访问次数++后,当前key的访问次数还是最少访问次数)this.minFreq++;}// 当前key访问次数++node.freq++;// 将当前key对应的node转移到对应增加后的访问次数对应的链表尾部(最近访问)this.freqMap.putIfAbsent(node.freq, new Link());this.freqMap.get(node.freq).addLast(node);}
}

JS算法源码

/** 双向链表节点 */
class Node {constructor(key, val, freq) {/** 记录本题的键 */this.key = key;/** 记录本题的值 */this.val = val;/** 记录该键被访问的次数 */this.freq = freq;/** 当前节点的上一个节点 */this.prev = null;/** 当前节点的下一个节点 */this.next = null;}
}/** 双向链表 */
class Link {constructor() {/** 链表中节点个数 */this.size = 0;/** 链表头节点 */this.head = null;/** 链表尾节点 */this.tail = null;}/*** 尾插* @param {*} node 要被插入的节点*/addLast(node) {if (this.size == 0) {// 空链表,则node节点插入后,即为头、尾节点this.head = node;this.tail = node;} else {// 非空链表,则node节点插入到tail节点后面this.tail.next = node;node.prev = this.tail;this.tail = node;}this.size++;}/*** 删除指定节点* @param {*} node 要删除的节点*/remove(node) {// 空链表没有节点,所以无法删除if (this.size == 0) return;if (this.size == 1) {// 链表只有一个节点,则删除完后,变为空链表this.head = null;this.tail = null;} else if (this.head == node) {// 如果要删除的节点是头节点this.head = this.head.next;this.head.prev = null;} else if (this.tail == node) {// 如果要删除的节点是尾节点this.tail = this.tail.prev;this.tail.next = null;} else {// 如果要删除的节点是中间节点node.prev.next = node.next;node.next.prev = node.prev;}this.size--;}
}/*** @param {number} capacity*/
var LFUCache = function (capacity) {/** keyMap用于记录key对应的node */this.keyMap = new Map();/** freqMap的key是访问次数,value是具有相同访问次数的key对应的node组成的链表,链表头是最远访问的,链表尾是最近访问的 */this.freqMap = new Map();/** LFU缓存中能记录的最多key的数量 */this.capacity = capacity;/** LFU缓存中所有的key中最少的访问次数 */this.minFreq = 0;
};/*** @param {number} key* @return {number}*/
LFUCache.prototype.get = function (key) {if (this.keyMap.has(key)) {// 存在对应key,则返回对应valconst node = this.keyMap.get(key);this.incNodeFreq(node); // get操作会新增对应key的访问次数return node.val;} else {// 不存在对应key,则返回-1return -1;}
};/*** @param {number} key* @param {number} value* @return {void}*/
LFUCache.prototype.put = function (key, value) {// 对应key已存在,则为更新场景if (this.keyMap.has(key)) {const node = this.keyMap.get(key);this.incNodeFreq(node); // 更新操作会增加对应key的访问次数node.val = value;}// 对应key不存在,则为新增场景else {// 先判断容量是否超过,keyMap的key数量就是LFU缓存中记录的key数量if (this.keyMap.size >= this.capacity) {const link = this.freqMap[this.minFreq];const removeKey = link.head.key;link.remove(link.head); // 最少访问次数所在链表的头节点,即为最少、最远访问的key,容量不足时,需要优先删除它this.keyMap.delete(removeKey); // 注意,不要遗漏将keyMap中该key删除}// 新增key,则对应key的访问次数为1,且为最少访问次数this.minFreq = 1;// 将新增key对应的node加入到freqMap,和keyMap中const node = new Node(key, value, this.minFreq);if (this.freqMap[this.minFreq] == undefined) {this.freqMap[this.minFreq] = new Link();}this.freqMap[this.minFreq].addLast(node);this.keyMap.set(key, node);}
};/*** 增加key的访问次数* @param {*} node key对应的node*/
LFUCache.prototype.incNodeFreq = function (node) {// 由于key的访问次数增加,因此要从原访问次数的链表中删除this.freqMap[node.freq].remove(node);// 如果原链表删除当前key对应的节点后为空,且原链表对应的访问次数就是最少访问次数if (this.freqMap[node.freq].size == 0 && node.freq == this.minFreq) {// 则最少访问次数对应的key没有了,因此最少访问次数++(即当前key访问次数++后,当前key的访问次数还是最少访问次数)this.minFreq++;}// 当前key访问次数++node.freq++;// 将当前key对应的node转移到对应增加后的访问次数对应的链表尾部(最近访问)if (this.freqMap[node.freq] == undefined) {this.freqMap[node.freq] = new Link();}this.freqMap[node.freq].addLast(node);
};

Python算法源码

# 双向链表节点
class Node:def __init__(self, key, val, freq):""":param key: 记录本题的键:param val: 记录本题的值:param freq: 记录该键被访问的次数"""self.key = keyself.val = valself.freq = freqself.prev = Noneself.next = None# 双向链表
class Link:def __init__(self):self.size = 0self.head = Noneself.tail = Nonedef addLast(self, node):"""尾插:param node: 要被插入的节点"""if self.size == 0:# 空链表,则node节点插入后,即为头、尾节点self.head = nodeself.tail = nodeelse:# 非空链表,则node节点插入到tail节点后面self.tail.next = nodenode.prev = self.tailself.tail = nodeself.size += 1def remove(self, node):"""删除指定节点:param node: 要删除的节点"""if self.size == 0:# 空链表没有节点,所以无法删除returnif self.size == 1:# 链表只有一个节点,则删除完后,变为空链表self.head = Noneself.tail = Noneelif self.head == node:# 如果要删除的节点是头节点self.head = self.head.nextself.head.prev = Noneelif self.tail == node:# 如果要删除的节点是尾节点self.tail = self.tail.prevself.tail.next = Noneelse:# 如果要删除的节点是中间节点node.prev.next = node.nextnode.next.prev = node.prevself.size -= 1class LFUCache(object):def __init__(self, capacity):self.keyMap = {}  # keyMap用于记录key对应的nodeself.freqMap = {}  # freqMap的key是访问次数,value是具有相同访问次数的key对应的node组成的链表,链表头是最远访问的,链表尾是最近访问的self.capacity = capacity  # LFU缓存中能记录的最多key的数量self.minFreq = 0  # LFU缓存中所有的key中最少的访问次数def get(self, key):if key in self.keyMap:# 存在对应key,则返回对应valnode = self.keyMap[key]self.incNodeFreq(node)  # get操作会新增对应key的访问次数return node.valelse:# 不存在对应key,则返回-1return -1def put(self, key, value):if key in self.keyMap:# 对应key已存在,则为更新场景node = self.keyMap[key]self.incNodeFreq(node)  # 更新操作会增加对应key的访问次数node.val = valueelse:# 对应key不存在,则为新增场景# 先判断容量是否超过,keyMap的key数量就是LFU缓存中记录的key数量if len(self.keyMap) >= self.capacity:link = self.freqMap[self.minFreq]removeKey = link.head.keylink.remove(link.head)  # 最少访问次数所在链表的头节点,即为:最少、最远访问的key,容量不足时,需要优先删除它self.keyMap.pop(removeKey)  # 注意,不要遗漏将keyMap中该key删除# 新增key,则对应key的访问次数为1,且为最少访问次数self.minFreq = 1node = Node(key, value, self.minFreq)# 将新增key对应的node加入到freqMap,和keyMap中self.freqMap.setdefault(self.minFreq, Link())self.freqMap.get(self.minFreq).addLast(node)self.keyMap[key] = nodedef incNodeFreq(self, node):"""增加key的访问次数:param node: key对应的node"""# 由于key的访问次数增加,因此要从原访问次数的链表中删除self.freqMap[node.freq].remove(node)# 如果原链表删除当前key对应的节点后为空,且原链表对应的访问次数就是最少访问次数if self.freqMap[node.freq].size == 0 and node.freq == self.minFreq:# 则最少访问次数对应的key没有了,因此最少访问次数++(即当前key访问次数++后,当前key的访问次数还是最少访问次数)self.minFreq += 1# 当前key访问次数++node.freq += 1# 将当前key对应的node转移到对应增加后的访问次数对应的链表尾部(最近访问)self.freqMap.setdefault(node.freq, Link())self.freqMap[node.freq].addLast(node)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/233036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【迁移学习论文四】Multi-Adversarial Domain Adaptation论文原理及复现工作

Multi-Adversarial Domain Adaptation 多对抗域适应 前言 好久没有更新了&#xff0c;所以这周开始记录下来&#xff0c;也好督促自己。记录本人预备研究生阶段相关迁移学习论文的原理阐述以及复现工作。 问题 跨域混淆或错误对齐 文章介绍 这篇文章于2018年发表在AAAI&…

手把手教你使用Cypress进行端到端测试

一、引言 Cypress是一个流行的端到端测试框架&#xff0c;它提供了一个全面的解决方案&#xff0c;可以测试任何在浏览器中运行的内容。不论你是想为一个小型项目添加测试&#xff0c;还是在大型企业级应用中进行端到端测试&#xff0c;Cypress都是一个不错的选择。本文将会手…

智能优化算法应用:基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.阿基米德优化算法4.实验参数设定…

国家开放大学 河南开放大学形成性考核 平时作业 统一参考资料

试卷代号&#xff1a;1258 房屋建筑混凝土结构设计 参考试题 一、单项选择题&#xff08;每小题2分&#xff0c;共计40分&#xff09; 1.( )是将框架结构中的部分跨间布置剪力墙或把剪力墙结构的部分剪力墙抽掉改为框架承重。 A.梁板结构体系 B.框…

评价机器学习模型的指标

为了衡量一个机器学习模型的好坏&#xff0c;需要给定一个测试集&#xff0c;用模型对测试集中的每一个样本进行预测&#xff0c;并根据预测结果计算评价分数。 对于分类问题&#xff0c;常见的评价标准有准确率、精确率、召回率和F值等。给定测试集 &#x1d4af; {(&#x1…

一款电压检测LVD

一、基本概述 The TX61C series devices are a set of three terminal low power voltage detectors implemented in CMOS technology. Each voltage detector in the series detects a particular fixed voltage ranging from 0.9V to 5.0V. The voltage detectors consist…

git缓存区、本地仓库、远程仓库的同步问题(初始化库无法pull和push)

git新建库与本地库同步 gitee使用教程&#xff0c;git的下载与安装接不在叙述了。 新建远程仓库 新建远程仓库必须要使用仓库提供的api&#xff0c;也就是仓库门户网站&#xff0c;例如gitee&#xff0c;github&#xff0c;gitlab等。在上图中使用gitee网址中新建了一个test仓…

回归预测 | MATLAB实现NGO-SCN北方苍鹰算法优化随机配置网络的数据回归预测 (多指标,多图)

回归预测 | MATLAB实现NGO-SCN北方苍鹰算法优化随机配置网络的数据回归预测 &#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现NGO-SCN北方苍鹰算法优化随机配置网络的数据回归预测 &#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介绍…

HarmonyOS(十五)——状态管理之@Prop装饰器(父子单向同步)

上一篇文章我们认识了状态管理的State装饰器&#xff08;组件内状态&#xff09;&#xff0c;接下来我们学习另外一个状态管理装饰器Prop装饰器。 Prop装饰的变量可以和父组件建立单向的同步关系。Prop装饰的变量是可变的&#xff0c;但是变化不会同步回其父组件。 说明&#…

10分钟微调专属于自己的大模型

本文主要介绍使用魔搭社区轻量级训练推理工具SWIFT&#xff0c;进行大模型自我认知微调&#xff0c;帮助初阶炼丹师快速微调出专属于自己的大模型。 SWIFT&#xff08;Scalable lightWeight Infrastructure for Fine-Tuning&#xff09;是魔搭ModelScope开源社区推出的一套完整…

泰坦陨落2找不到msvcr120文件的修复方法,分享多种解决方法

在玩泰坦陨落2这款游戏时&#xff0c;有些玩家可能会遇到找不到msvcr120.dll文件的问题。这个问题可能是由于游戏缺少必要的运行库导致的。下面我将分享一些解决这个问题的方法&#xff0c;希望对大家有所帮助。 一、问题分析 msvcr120.dll是Microsoft Visual C Redistributab…

MATLAB - 使用 MPC Designer 线性化 Simulink 模型

系列文章目录 前言 本主题介绍如何使用 MPC Designer 对 Simulink 模型进行线性化。为此&#xff0c;请从包含 MPC 控制器块的 Simulink 模型打开该应用程序。本例中使用 CSTR_ClosedLoop 模型。 open_system(CSTR_ClosedLoop) 在模型窗口中&#xff0c;双击 MPC 控制器模块。…

Vue中英文翻译小结

背景&#xff1a;时局艰难&#xff0c;后端开发被强制写了vue&#xff0c;这不有个需求是中英文翻译&#xff0c;特此记录下&#xff0c;该怎么个翻译法子。 先引入全局的路由国际化文件&#xff0c;zh.js 和 en.js 1.关于插值表达Button里面 {{ $t(reinsop.common.back) }} …

LazyIDA源码阅读

LazyIDA是一款IDA插件&#xff0c;项目地址GitHub - L4ys/LazyIDA: Make your IDA Lazy! 外部引用 from __future__ import division from __future__ import print_function from struct import unpack import idaapi import idautils import idcfrom PyQt5.Qt import QAppli…

vue中的事件修饰符、表单双向数据绑定和计算属性

目录 一、事件修饰符 二、表单双向数据绑定 模拟双向数据绑定&#xff08;双向数据绑定底层原理&#xff09; 三、计算属性 计算属性和methods方法区别&#xff1f; 计算属性和watch区别&#xff1f; 一、事件修饰符 stop 阻止事件冒泡 prevent 阻止事件默认行为 ca…

Linux线程——互斥锁

概念 互斥量&#xff08;mutex&#xff09;从本质上来说是一把锁&#xff0c;在访问共享资源前对互斥量进行加锁&#xff0c;在访问完成后释放互斥量上的锁。对互斥量进行加锁后&#xff0c;任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。 如果释放…

【HCIP学习记录】OSPF之DD报文

1.OSPF报文格式 24字节 字段长度含义Version1字节版本&#xff0c;OSPF的版本号。对于OSPFv2来说&#xff0c;其值为2。Type1字节类型&#xff0c;OSPF报文的类型&#xff0c;有下面几种类型&#xff1a; 1&#xff1a;Hello报文&#xff1b;● 2&#xff1a;DD报文&#xff1…

美国联邦机动车安全标准-FMVSS

FMVSS标准介绍&#xff1a; FMVSS是美国《联邦机动车安全标准》&#xff0c;由美国运输部下属的国家公路交通安全管理局(简称NHTSA)具体负责制定并实施。是美国联邦政府针对机动车制定的安全标准&#xff0c;旨在提高机动车的安全性能&#xff0c;减少交通事故中的人员伤亡。F…

ubuntu无 root 权限安装 screen

网上的方法主要是如下图的方法&#xff0c;源码安装&#xff0c;但是我一直 make install失败显示没有权限 然后选择放弃&#xff0c;然后随便试了一下方法 2&#xff0c;成功 方法 1 方法 2 pip3 install screen结果&#xff1a;

生物识别应用指纹的算法是什么样的?有什么性能?

方案特点 • 采用金融级安全芯片 ACH512 的指纹模组&#xff0c;指纹和密码安全存储&#xff0c;云端数据安全传输 • 采用高性能指纹专用安全MCU芯片ACM32FP4&#xff0c;支持小点阵图像算法处理 • 支持80*64、88*112、96*96、160*160、192*192等像素传感器 • 已适配传…