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;}
}