【问题描述】

设计并实现最不经常使用(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来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lfu-cache

【解答思路】

1. 核心思想:先考虑访问次数,在访问次数相同的情况下,再考虑缓存的时间


  • 每次访问一个已经存在的元素的时候:应该先把结点类从当前所属的访问次数双链表里删除,然后再添加到它「下一个访问次数」的双向链表的头部。

时间复杂度:O(1) 空间复杂度:O(N)

import java.util.HashMap;
import java.util.Map;public class LFUCache {/*** key 就是题目中的 key* value 是结点类*/private Map<Integer, ListNode> map;/*** 访问次数哈希表,使用 ListNode[] 也可以,不过要占用很多空间*/private Map<Integer, DoubleLinkedList> frequentMap;/*** 外部传入的容量大小*/private Integer capacity;/*** 全局最高访问次数,删除最少使用访问次数的结点时会用到(这个设计可能是冗余的)*/private Integer maxFrequent = 1;public LFUCache(int capacity) {map = new HashMap<>(capacity);frequentMap = new HashMap<>();this.capacity = capacity;}/*** get 一次操作,访问次数就增加 1;* 从原来的链表调整到访问次数更高的链表的表头** @param key* @return*/public int get(int key) {// 测试测出来的,capacity 可能传 0if (capacity == 0) {return -1;}if (map.containsKey(key)) {// 获得结点类ListNode listNode = removeListNode(key);// 挂接到新的访问次数的双向链表的头部int frequent = listNode.frequent;addListNode2Head(frequent, listNode);return listNode.value;} else {return -1;}}/*** @param key* @param value*/public void put(int key, int value) {// 如果 key 存在,就更新访问次数 + 1,更新值if (map.containsKey(key)) {ListNode listNode = removeListNode(key);// 更新 valuelistNode.value = value;int frequent = listNode.frequent;addListNode2Head(frequent, listNode);return;}// 如果 key 不存在// 1、如果满了,先删除访问次数最小的的末尾结点,再删除 map 里对应的 keyif (map.size() == capacity) {for (int i = 1; i <= maxFrequent; i++) {if (frequentMap.containsKey(i) && frequentMap.get(i).count > 0) {// 1、从双链表里删除结点DoubleLinkedList doubleLinkedList = frequentMap.get(i);ListNode removeNode = doubleLinkedList.removeTail();// 2、删除 map 里对应的 keymap.remove(removeNode.key);break;}}}// 2、再创建新结点放在访问次数为 1 的双向链表的前面ListNode newListNode = new ListNode(key, value);addListNode2Head(1, newListNode);map.put(key, newListNode);}// 以下部分主要是结点类和双向链表的操作/*** 结点类,是双向链表的组成部分*/private class ListNode {private int key;private int value;private int frequent = 1;private ListNode pre;private ListNode next;public ListNode() {}public ListNode(int key, int value) {this.key = key;this.value = value;}}/*** 双向链表*/private class DoubleLinkedList {/*** 虚拟头结点,它无前驱结点*/private ListNode dummyHead;/*** 虚拟尾结点,它无后继结点*/private ListNode dummyTail;/*** 当前双向链表的有效结点数*/private int count;public DoubleLinkedList() {this.dummyHead = new ListNode(-1, -1);this.dummyTail = new ListNode(-1, -1);dummyHead.next = dummyTail;dummyTail.pre = dummyHead;count = 0;}/*** 把一个结点类添加到双向链表的开头(头部是最新使用数据)** @param addNode*/public void addNode2Head(ListNode addNode) {ListNode oldHead = dummyHead.next;// 两侧结点指向它dummyHead.next = addNode;oldHead.pre = addNode;// 它的前驱和后继指向两侧结点addNode.pre = dummyHead;addNode.next = oldHead;count++;}/*** 把双向链表的末尾结点删除(尾部是最旧的数据,在缓存满的时候淘汰)** @return*/public ListNode removeTail() {ListNode oldTail = dummyTail.pre;ListNode newTail = oldTail.pre;// 两侧结点建立连接newTail.next = dummyTail;dummyTail.pre = newTail;// 它的两个属性切断连接oldTail.pre = null;oldTail.next = null;count--;return oldTail;}}/*** 将原来访问次数的结点,从双向链表里脱离出来** @param key* @return*/private ListNode removeListNode(int key) {// 获得结点类ListNode deleteNode = map.get(key);ListNode preNode = deleteNode.pre;ListNode nextNode = deleteNode.next;// 两侧结点建立连接preNode.next = nextNode;nextNode.pre = preNode;// 删除去原来两侧结点的连接deleteNode.pre = null;deleteNode.next = null;// 维护双链表结点数frequentMap.get(deleteNode.frequent).count--;// 访问次数加 1deleteNode.frequent++;maxFrequent = Math.max(maxFrequent, deleteNode.frequent);return deleteNode;}/*** 把结点放在对应访问次数的双向链表的头部** @param frequent* @param addNode*/private void addListNode2Head(int frequent, ListNode addNode) {DoubleLinkedList doubleLinkedList;// 如果不存在,就初始化if (frequentMap.containsKey(frequent)) {doubleLinkedList = frequentMap.get(frequent);} else {doubleLinkedList = new DoubleLinkedList();}// 添加到 DoubleLinkedList 的表头doubleLinkedList.addNode2Head(addNode);frequentMap.put(frequent, doubleLinkedList);}
}作者:liweiwei1419
链接:https://leetcode-cn.com/problems/lfu-cache/solution/ha-xi-biao-shuang-xiang-lian-biao-java-by-liweiwei/

测试用例

public class LFUCache {public static void main(String[] args) {LFUCache cache = new LFUCache(3);cache.put(1, 1);cache.put(2, 2);cache.put(3, 3);System.out.println(cache.map.keySet());cache.put(4, 4);System.out.println(cache.map.keySet());int res1 = cache.get(4);System.out.println(res1);int res2 = cache.get(3);System.out.println(res2);int res3 = cache.get(2);System.out.println(res3);int res4 = cache.get(1);System.out.println(res4);cache.put(5, 5);int res5 = cache.get(1);System.out.println(res5);int res6 = cache.get(2);System.out.println(res6);int res7 = cache.get(3);System.out.println(res7);int res8 = cache.get(4);System.out.println(res8);int res9 = cache.get(5);System.out.println(res9);}
}

【总结】

1.LFU (Least Frequently Used)缓存机制(看访问次数)
-在缓存满的时候,删除缓存里使用次数最少的元素,然后在缓存中放入新元素;
-数据的访问次数很重要,访问次数越多,就越不容易被删除;
-根据题意,「当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除」,即在「访问次数」相同的情况下,按照时间顺序,先删除在缓存里时间最久的数据

2.编码总结

3.功力仍未够深厚 只能理解其思想

文章转载链接: https://leetcode-cn.com/problems/lfu-cache/solution/ha-xi-biao-shuang-xiang-lian-biao-java-by-liweiwei/

[Leedcode][JAVA][第460题][LFU]相关推荐

  1. [Leedcode][JAVA][第105题][从前序与中序遍历序列构造二叉树][栈][递归][二叉树]

    [问题描述][中等] 根据一棵树的前序遍历与中序遍历构造二叉树.注意: 你可以假设树中没有重复的元素.例如,给出前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = ...

  2. [Leedcode][JAVA][第470题][Ran7()实现Rand10()]

    [问题描述][Leedcode][JAVA][第470题][Ran7()实现Rand10()] 已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 ...

  3. [Leedcode][JAVA][第45题][跳跃游戏 II][贪心算法]

    [问题描述][Leedcode][JAVA][第45题][跳跃游戏 II] 输入: [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2.从下标为 0 跳到下标为 1 的位置 ...

  4. [Leedcode][JAVA][第146题][LRU][哈希表][双向链表]

    [问题描述] LFU 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put .获取数据 get(key) - 如果密 ...

  5. [Leedcode][JAVA][第300题][最长上上子序列][动态规划][压缩空间]

    [问题描述][中等] 给定一个无序的整数数组,找到其中最长上升子序列的长度.示例:输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它 ...

  6. [Leedcode][JAVA][第680题][验证回文字符串Ⅱ][贪心][递归]

    [问题描述][第680题][验证回文字符串Ⅱ][简单] 给定一个非空字符串 s,最多删除一个字符.判断是否能成为回文字符串.示例 1:输入: "aba" 输出: True 示例 2 ...

  7. [Leedcode][JAVA][第210 题][课程表 II][拓扑排序][BFS][DFS][有向图]

    [问题描述][第210 题][课程表 II][中等] 现在你总共有 n 门课需要选,记为 0 到 n-1.在选修某些课程之前需要一些先修课程. 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用 ...

  8. [Leedcode][JAVA][第25题][K个一组反转链表][链表][递归]

    [问题描述][第25题][K个一组反转链表][困难] 时间复杂度:O(N^2) 空间复杂度:O(1) ```java 给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表.k 是一个正整数, ...

  9. [Leedcode][JAVA][第560题][和为K的子数组][Hashmap][数组]

    [问题描述][第560题][和为K的子数组][中等] 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数.示例 1 :输入:nums = [1,1,1], k = 2 输 ...

最新文章

  1. 14Web APIs简介
  2. iOS开发UI篇—手写控件,frame,center和bounds属性
  3. p750tm安装linux系统,Android 开发环境(虚拟机,LINUX, secureCRT)安装过程.pdf
  4. 硅谷蓝图创始人 Patrick:数据驱动规模化增长
  5. 将一个信号同步到clk中的通式
  6. 虹软java接摄像头_虹软人脸识别SDK(java+linux/window) 初试
  7. Linux进程实践(5) --守护进程
  8. jni直接转byte_JNI jbyteArray转char*
  9. 发布 学习进度条 博客要求
  10. Python pycurl使用
  11. 总结:MySQL备份与恢复的三种方法
  12. 吐槽一下:武装GoldenDict时,好一个OALD,RAR格式,12万多的文件,晕!!
  13. 软件项目开发计划编制过程
  14. 桌面图标有蓝底处理刚才
  15. 多次重复原生进入RN优化Catalyst Instance has already disappeared
  16. 云更新网吧系统服务器,云更新网吧无盘
  17. 网店三大要素:产品、运营与品牌
  18. The client-side rendered virtual DOM tree is not matching server-rendered content
  19. android psensor测试,android传感器Gsensor和Psensor的使用举例
  20. 自动化控制重要国际学术会议

热门文章

  1. 【Linux操作系统分析】设备驱动处理流程
  2. 最有价值的100句话
  3. php地址转换成经纬度,百度地图 获取地址转换为经纬度
  4. vue 实现页面静态化
  5. elasticSearch5.x与mysql数据库同步
  6. Activity中 onResume和onPause与onStart()和onStop()的一些思考
  7. Android 图片识别、图像识别
  8. 支付宝小程序中Navigator和导航栏之间的区别以及用法场景的分析
  9. dfa转正则表达式_从0到1打造正则表达式执行引擎(二)
  10. java web开发之 spring单元测试