100道面试必会算法-11-LFU缓存
题目描述
最不经常使用(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
解题思路
与LRU缓存有些类似,加入了计数器,直接看了题解,有多种解法,也未动手敲,此处埋坑~~~~~~~~~~~~~~~~~~~~
链接:https://leetcode.cn/problems/lfu-cache/solutions/48636/java-13ms-shuang-100-shuang-xiang-lian-biao-duo-ji/
这个方法较好理解
O(logN) 解法
 O(logN) 解法 —— 使用小根堆找到 freq 最小,因为 Java 中的 PriorityQueue 默认就是小根堆, 实现最简单
 每次将访问频次 freq 最小的且最先访问的上浮到堆顶,下面用全局自增 idx 表示访问的先后,或者可以直接改成 idx = System.nanoTime() 用以比较访问时间的先后。
代码
class LFUCache {Map<Integer, Node> cache;Queue<Node> queue;int capacity;int size;int idx = 0;public LFUCache(int capacity) {cache = new HashMap<>(capacity);if (capacity > 0) {queue = new PriorityQueue<>(capacity);}this.capacity = capacity;}public int get(int key) {Node node = cache.get(key);if (node == null) {return -1;}node.freq++;node.idx = idx++;queue.remove(node);queue.offer(node);return node.value;}public void put(int key, int value) {if (capacity == 0) {return;}Node node = cache.get(key);if (node != null) {node.value = value;node.freq++;node.idx = idx++;queue.remove(node);queue.offer(node);} else {if (size == capacity) {cache.remove(queue.peek().key);queue.poll();size--;} Node newNode = new Node(key, value, idx++);cache.put(key, newNode);queue.offer(newNode);size++;}}
}class Node implements Comparable<Node> {int key;int value;int freq;int idx;public Node() {}public Node(int key, int value, int idx) {this.key = key;this.value = value;freq = 1;this.idx = idx;}public int compareTo(Node node) {int diff = freq - node.freq;return diff != 0? diff: idx - node.idx;}
}