题目链接
题目描述
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构(least frequently used)。
实现 LFUCache
类:
LFUCache(int capacity)
- 用数据结构的容量capacity
初始化对象int get(int key)
- 如果键存在于缓存中,则获取键的值,否则返回 -1。void put(int key, int value)
- 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。
注意「项的使用次数」就是自插入该项以来对其调用 get
和 put
函数的次数之和。使用次数会在对应项被移除后置为 0 。
为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
当一个键首次插入到缓存中时,它的使用计数器被设置为 1
(由于 put 操作)。对缓存中的键执行 get
或 put
操作,使用计数器的值将会递增。
示例:
输入:
[“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 lFUCache = new LFUCache(2);
lFUCache.put(1, 1); // cache=[1,_], cnt(1)=1
lFUCache.put(2, 2); // cache=[2,1], cnt(2)=1, cnt(1)=1
lFUCache.get(1); // 返回 1
// cache=[1,2], cnt(2)=1, cnt(1)=2
lFUCache.put(3, 3); // 去除键 2 ,因为 cnt(2)=1 ,使用计数最小
// cache=[3,1], cnt(3)=1, cnt(1)=2
lFUCache.get(2); // 返回 -1(未找到)
lFUCache.get(3); // 返回 3
// cache=[3,1], cnt(3)=2, cnt(1)=2
lFUCache.put(4, 4); // 去除键 1 ,1 和 3 的 cnt 相同,但 1 最久未使用
// cache=[4,3], cnt(4)=1, cnt(3)=2
lFUCache.get(1); // 返回 -1(未找到)
lFUCache.get(3); // 返回 3
// cache=[3,4], cnt(4)=1, cnt(3)=3
lFUCache.get(4); // 返回 4
// cache=[3,4], cnt(4)=2, cnt(3)=3
提示:
0 <= capacity, key, value <= 104
- 最多调用
105
次get
和put
方法
进阶: 你可以为这两种操作设计时间复杂度为 O(1)
的实现吗?
解题思路
方法一:哈希表 + 链表(超时)
节点中有 key value 和使用频率
通过哈希表记录当前存在的节点
对于新加入的节点和新使用过的节点,移动到链表最前面(越靠前越近被使用过LRU)并使用频率加一
插入节点
- 判断当前节点key是否存在,如果存在 则 更新 value 使用频率加一 并将节点移动到链表首部
- 如果存在则判断当前 size 是否达到容量,如果没达到 添加节点并移动到链表首部 size++
- 如果达到容量,将使用频率最小的节点删除 添加节点。如果频率最小的节点不唯一,则删除链表最后一个使用频率最小的节点 ```java
class LFUCache {
class CacheNode{
private int key;
private int value;
private int frequency;
CacheNode next;
CacheNode pre;
CacheNode(){
this.key = 0;
this.value = 0;
this.frequency = 0;
next = null;
pre = null;
}
CacheNode(int key, int value){
this.key = key;
this.value = value;
this.frequency = 0;
next = null;
pre = null;
}
}
private int capacity;
private Map
public int get(int key) {
if(capacity == 0){
return -1;
}
if(cache.containsKey(key)){
CacheNode node = cache.get(key);
++node.frequency;
moveToHead(node);
return node.value;
}
return -1;
}
public void put(int key, int value) {
// 存在 key 替换结果
if(capacity == 0){
return;
}
if(cache.containsKey(key)){
CacheNode node = cache.get(key);
++node.frequency;
node.value = value;
moveToHead(node);
}else{
// 创建节点
CacheNode node = new CacheNode(key, value);
// 容量满了 删除一个
if(size == capacity){
CacheNode cur = head.next;
int minVal = head.next.frequency;
CacheNode minNode = head.next;
int num = 0;
while(cur != tail){
// 记录使用频率最少的节点
if(cur.frequency < minVal){
minVal = cur.frequency;
minNode = cur;
num = 1;
}else if(cur.frequency == minVal){
// 频率相同时 选最长时间没被使用的节点
minNode = cur;
++num;
}
cur = cur.next;
}
// 删除节点
cache.remove(minNode.key);
deleteNode(minNode);
// 添加节点
cache.put(key, node);
addToHead(node);
}else{
++size;
cache.put(key, node);
addToHead(node);
}
}
}
private void moveToHead(CacheNode node){
deleteNode(node);
addToHead(node);
}
private void addToHead(CacheNode node){
node.next = head.next;
node.pre = head;
head.next.pre = node;
head.next = node;
}
private void deleteNode(CacheNode node){
CacheNode preNode = node.pre;
preNode.next = node.next;
node.next.pre = preNode;
node.next = null;
node.pre = null;
}
}
/**
- Your LFUCache object will be instantiated and called as such:
- LFUCache obj = new LFUCache(capacity);
- int param_1 = obj.get(key);
- obj.put(key,value); */ ```
- 时间复杂度 O(n)
-
方法二:哈希表 + 小顶堆(Lambda表达式或者compareTo)
利用重写compareTo 对优先队列进行排序,堆顶永远是 使用频率最小 或者 频率相同 使用顺序最小(最早使用) 的节点
通过 idx 表示访问的先后 ```java class LFUCache {Map
cache; Queue queue; int capacity; int size; // idx 表示访问的先后 int idx = 0; public LFUCache(int capacity) {
cache = new HashMap<>(capacity); if (capacity > 0) { queue = new PriorityQueue<>(capacity); // 排序推荐用 lambda表达式实现 // queue = new PriorityQueue<Node>((a, b)-> a.freq - b.freq == 0 ? a.idx - b.idx:a.freq - b.freq); } this.capacity = capacity;
}
public int get(int key) {
// 不存在 if (!cache.containsKey(key)) { return -1; } Node node = cache.get(key); node.freq++; node.idx = idx++; // 删除node 再添加 为了重新排序 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
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;
}
}
```java
class LFUCache {
Map<Integer, Node> cache;
Queue<Node> queue;
int capacity;
int size;
// idx 表示访问的先后
int idx = 0;
public LFUCache(int capacity) {
this.capacity = capacity;
cache = new HashMap<Integer, Node>();
// 重写比较函数 频率不同时按频率排序 频率相同时按先后顺序排序
queue = new PriorityQueue<Node>((a, b) -> {
return a.freq != b.freq ? a.freq - b .freq : a.idx - b.idx;
});
}
public int get(int key) {
// 不存在
if (!cache.containsKey(key)) {
return -1;
}
Node node = cache.get(key);
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.freq++;
node.idx = idx++;
node.value = value;
queue.remove(node);
queue.offer(node);
}else{
if(capacity <= cache.size()){
cache.remove(queue.peek().key);
queue.poll();
}
node = new Node(key, value, idx++);
cache.put(key, node);
queue.offer(node);
}
}
class 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;
}
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache obj = new LFUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
- 时间复杂度 O(n log n)
-
方法三:双哈希表
利用重写compareTo 对优先队列进行排序,堆顶永远是 使用频率最小 或者 频率相同 使用顺序最小(最早使用) 的节点
通过 idx 表示访问的先后 ```java class LFUCache { int minfreq, capacity; Mapkey_table; Map > freq_table; public LFUCache(int capacity) {
this.minfreq = 0; this.capacity = capacity; key_table = new HashMap<Integer, Node>();; freq_table = new HashMap<Integer, LinkedList<Node>>();
}
public int get(int key) {
if (capacity == 0) { return -1; } if (!key_table.containsKey(key)) { return -1; } Node node = key_table.get(key); int val = node.val, freq = node.freq; freq_table.get(freq).remove(node); // 如果当前链表为空,我们需要在哈希表中删除,且更新minFreq if (freq_table.get(freq).size() == 0) { freq_table.remove(freq); if (minfreq == freq) { minfreq += 1; } } // 插入到 freq + 1 中 LinkedList<Node> list = freq_table.getOrDefault(freq + 1, new LinkedList<Node>()); list.offerFirst(new Node(key, val, freq + 1)); freq_table.put(freq + 1, list); key_table.put(key, freq_table.get(freq + 1).peekFirst()); return val;
}
public void put(int key, int value) {
if (capacity == 0) { return; } if (!key_table.containsKey(key)) { // 缓存已满,需要进行删除操作 if (key_table.size() == capacity) { // 通过 minFreq 拿到 freq_table[minFreq] 链表的末尾节点 Node node = freq_table.get(minfreq).peekLast(); key_table.remove(node.key); freq_table.get(minfreq).pollLast(); if (freq_table.get(minfreq).size() == 0) { freq_table.remove(minfreq); } } LinkedList<Node> list = freq_table.getOrDefault(1, new LinkedList<Node>()); list.offerFirst(new Node(key, value, 1)); freq_table.put(1, list); key_table.put(key, freq_table.get(1).peekFirst()); minfreq = 1; } else { // 与 get 操作基本一致,除了需要更新缓存的值 Node node = key_table.get(key); int freq = node.freq; freq_table.get(freq).remove(node); if (freq_table.get(freq).size() == 0) { freq_table.remove(freq); if (minfreq == freq) { minfreq += 1; } } LinkedList<Node> list = freq_table.getOrDefault(freq + 1, new LinkedList<Node>()); list.offerFirst(new Node(key, value, freq + 1)); freq_table.put(freq + 1, list); key_table.put(key, freq_table.get(freq + 1).peekFirst()); }
} }
class Node { int key, val, freq;
Node(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
}
} ```
- 时间复杂度 O(1)
- 空间复杂度 O(capacity)