leecode/problems/460.lfu-cache.md
2020-05-22 18:17:19 +08:00

243 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 题目地址
https://leetcode.com/problems/lfu-cache/
## 题目描述
```
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
```
## 思路
`本题已被收录到我的新书中,敬请期待~`
[LFULeast frequently used)](https://www.wikiwand.com/en/Least_frequently_used) 但内存容量满的情况下,有新的数据进来,需要更多空间的时候,就需要删除被访问频率最少的元素。
举个例子,比如说 cache 容量是 3按顺序依次放入 `121213` cache 已存满 3 个元素 (123) 这时如果想放入一个新的元素 4 的时候,就需要腾出一个元素空间。
用 LFU这里就淘汰 3 因为 3 的次数只出现依次, 1 和 2 出现的次数都比 3 多。
题中 `get``put` 都是 `O(1)`的时间复杂度,那么删除和增加都是`O(1)`,可以想到用双链表,和`HashMap`,用一个`HashMap, nodeMap,` 保存当前`key`,和 `node{key, value, frequent} `的映射。
这样`get(key)`的操作就是`O(1)`. 如果要删除一个元素,那么就需要另一个`HashMapfreqMap`保存元素出现次数`frequent`和双链表`DoublyLinkedlist` 映射,
这里双链表存的是 frequent 相同的元素。每次`get``put`的时候,`frequent+1`,然后把`node`插入到双链表的`head node, head.next=node`
每次删除`freqent`最小的双链表的`tail node, tail.prev`
用给的例子举例说明:
```
1. put(1, 1),
- 首先查找 nodeMap 中有没有 key=1 对应的 value
没有就新建 node(key, value, freq) -> node1(1, 1, 1), 插入 nodeMap{[1, node1]}
- 查找 freqMap 中有没有 freq=1 对应的 value
没有就新建 doublylinkedlist(head, tail), 把 node1 插入 doublylinkedlist head->next = node1.
如下图,
```
![460.lfu-cache-1](../assets/problems/460.lfu-cache-1.jpg)
```
2. put(2, 2),
- 首先查找 nodeMap 中有没有 key=2 对应的 value
没有就新建 node(key, value, freq) -> node2(2, 2, 1), 插入 nodeMap{[1, node1], [2, node2]}
- 查找 freqMap 中有没有 freq=1 对应的 value
没有就新建 doublylinkedlist(head, tail), 把 node2 插入 doublylinkedlist head->next = node2.
如下图,
```
![460.lfu-cache-2](../assets/problems/460.lfu-cache-2.jpg)
```
3. get(1),
- 首先查找 nodeMap 中有没有 key=1 对应的 valuenodeMap:{[1, node1], [2, node2]},
找到 node1把 node1 freq+1 -> node1(1,1,2)
- 更新 freqMap删除 freq=1node1
- 更新 freqMap插入 freq=2node1
如下图,
```
![460.lfu-cache-3](../assets/problems/460.lfu-cache-3.jpg)
```
4. put(3, 3),
- 判断 cache 的 capacity已满需要淘汰使用次数最少的元素找到最小的 freq=1删除双链表 tail node.prev
如果 tailnode.prev != null, 删除。然后从 nodeMap 中删除对应的 key。
- 首先查找 nodeMap 中有没有 key=3 对应的 value
没有就新建 node(key, value, freq) -> node3(3, 3, 1), 插入 nodeMap{[1, node1], [3, node3]}
- 查找 freqMap 中有没有 freq=1 对应的 value
没有就新建 doublylinkedlist(head, tail), 把 node3 插入 doublylinkedlist head->next = node3.
如下图,
```
![460.lfu-cache-4](../assets/problems/460.lfu-cache-4.jpg)
```
5. get(2)
- 查找 nodeMap如果没有对应的 key 的 value返回 -1。
6. get(3)
- 首先查找 nodeMap 中有没有 key=3 对应的 valuenodeMap:{[1, node1], [3, node3]},
找到 node3把 node3 freq+1 -> node3(3,3,2)
- 更新 freqMap删除 freq=1node3
- 更新 freqMap插入 freq=2node3
如下图,
```
![460.lfu-cache-5](../assets/problems/460.lfu-cache-5.jpg)
```
7. put(4, 4),
- 判断 cache 的 capacity已满需要淘汰使用次数最少的元素找到最小的 freq=1删除双链表 tail node.prev
如果 tailnode.prev != null, 删除。然后从 nodeMap 中删除对应的 key。
- 首先查找 nodeMap 中有没有 key=4 对应的 value
没有就新建 node(key, value, freq) -> node4(4, 4, 1), 插入 nodeMap{[4, node4], [3, node3]}
- 查找 freqMap 中有没有 freq=1 对应的 value
没有就新建 doublylinkedlist(head, tail), 把 node4 插入 doublylinkedlist head->next = node4.
如下图,
```
![460.lfu-cache-6](../assets/problems/460.lfu-cache-6.jpg)
```
8. get(1)
- 查找 nodeMap如果没有对应的 key 的 value返回 -1。
9. get(3)
- 首先查找 nodeMap 中有没有 key=3 对应的 valuenodeMap:{[4, node4], [3, node3]},
找到 node3把 node3 freq+1 -> node3(3,3,3)
- 更新 freqMap删除 freq=2node3
- 更新 freqMap插入 freq=3node3
如下图,
```
![460.lfu-cache-7](../assets/problems/460.lfu-cache-7.jpg)
```
10. get(4)
- 首先查找 nodeMap 中有没有 key=4 对应的 valuenodeMap:{[4, node4], [3, node3]},
找到 node4把 node4 freq+1 -> node4(4,4,2)
- 更新 freqMap删除 freq=1node4
- 更新 freqMap插入 freq=2node4
如下图,
```
![460.lfu-cache-8](../assets/problems/460.lfu-cache-8.jpg)
## 关键点分析
用两个`Map`分别保存 `nodeMap {key, node}` 和 `freqMap{frequent, DoublyLinkedList}`。
实现`get` 和 `put`操作都是`O(1)`的时间复杂度。
可以用 Java 自带的一些数据结构,比如 HashLinkedHashSet这样就不需要自己自建 NodeDoublelyLinkedList。
可以很大程度的缩减代码量。
## 代码Java code
```java
public class LC460LFUCache {
class Node {
int key, val, freq;
Node prev, next;
Node(int key, int val) {
this.key = key;
this.val = val;
freq = 1;
}
}
class DoubleLinkedList {
private Node head;
private Node tail;
private int size;
DoubleLinkedList() {
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
}
void add(Node node) {
head.next.prev = node;
node.next = head.next;
node.prev = head;
head.next = node;
size++;
}
void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
size--;
}
// always remove last node if last node exists
Node removeLast() {
if (size > 0) {
Node node = tail.prev;
remove(node);
return node;
} else return null;
}
}
// cache capacity
private int capacity;
// min frequent
private int minFreq;
Map<Integer, Node> nodeMap;
Map<Integer, DoubleLinkedList> freqMap;
public LC460LFUCache(int capacity) {
this.minFreq = 0;
this.capacity = capacity;
nodeMap = new HashMap<>();
freqMap = new HashMap<>();
}
public int get(int key) {
Node node = nodeMap.get(key);
if (node == null) return -1;
update(node);
return node.val;
}
public void put(int key, int value) {
if (capacity == 0) return;
Node node;
if (nodeMap.containsKey(key)) {
node = nodeMap.get(key);
node.val = value;
update(node);
} else {
node = new Node(key, value);
nodeMap.put(key, node);
if (nodeMap.size() == capacity) {
DoubleLinkedList lastList = freqMap.get(minFreq);
nodeMap.remove(lastList.removeLast().key);
}
minFreq = 1;
DoubleLinkedList newList = freqMap.getOrDefault(node.freq, new DoubleLinkedList());
newList.add(node);
freqMap.put(node.freq, newList);
}
}
private void update(Node node) {
DoubleLinkedList oldList = freqMap.get(node.freq);
oldList.remove(node);
if (node.freq == minFreq && oldList.size == 0) minFreq++;
node.freq++;
DoubleLinkedList newList = freqMap.getOrDefault(node.freq, new DoubleLinkedList());
newList.add(node);
freqMap.put(node.freq, newList);
}
}
```
## 参考References
1. [LFU(Least frequently used) Cache](https://www.wikiwand.com/en/Least_frequently_used)
2. [Leetcode discussion mylzsd](https://leetcode.com/problems/lfu-cache/discuss/94547/Java-O(1)-Solution-Using-Two-HashMap-and-One-DoubleLinkedList)
3. [Leetcode discussion aaaeeeo](https://leetcode.com/problems/lfu-cache/discuss/94547/Java-O(1)-Solution-Using-Two-HashMap-and-One-DoubleLinkedList)