题目描述
实现 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) 的平均时间复杂度运行。
罗列条件
-
get(key),若key存在,则返回value,该key的计数器加1
-
get(key),若key不存在,则返回-1
-
put(key, value),若key存在,更新value,该key的计数器加1
-
put(key, value),若key不存在
- 若容量 capacity已满,将计数器最小删除,若存在多个,则最近最久未使用的值删除,将(key,value)存入容器,该key的计数器等于1
- 若容器 capacity未满,将(key,value)存入容器,该key的计数器等于1
-
get 和 put平均时间复杂度 必须是 O(1)
思考实现
- get 和 put时间复杂度为 O(1) ,最有可能用到HashMap
- key,value,计数器是几个关联性强的属性,可以单独定义在一个数据结构Node中,那么可以定义HashMap<key, Node>容器,完成get时间复杂度为 O(1)的要求
- put中存在删除,考虑时间复杂度,删除操作O(1),最有可能用到(双向)链表DoubleLinkedNode
- 删除要求计数器最小,如何知道计数器最小是多少,简单处理,定义个全局变量minFreq记录计数器最小
- 如何 O(1)时间内根据minFreq获取到它的Node,可以定义HashMap<Freq, Node>,考虑相同频率可能有多个,且有删除操作,所以变动下,定义HashMap<Freq, DoubleLinkedNode>结构
- 删除计数器最小时,若存在多个,则删除DoubleLinkedNode中最近最久未使用的值,如何 O(1)时间内删除最近最久未使用呢,只要 O(1)时间内查找到最近最久未使用,那就可以实现,因为链表删除操作时间复杂度就是O(1)
- 如何O(1)时间内查找到DoubleLinkedNode中最近最久未使用,最简单的做法,将DoubleLinkedNode的头结点或者尾结点专门存最近最久未使用的Node,那查找起来时间为O(1),一般是尾结点,为什么这么设置,因为每次新访问的值,在头部插入,越靠前,越是最近访问,越靠后越是最久(未使用)访问,最久未使用的必然是尾结点
- 考虑get的实现流程,不存在的流程不做说明,存在key时,key对应的node的计数器值要加1,计数器加1后,它就需要从HashMap<Freq, DoubleLinkedNode>结构挪位置,不能再原来的计数器链表中存储了,需要从里面删除掉,然后插入加1后的计数器链表中,这里容易遗漏一个细节,若原来计数器所在链表一个node都没有,需要从HashMap<Freq, DoubleLinkedNode>删掉。此时还需要判断是否minFreq是否是这个计数器值,若是则要加1
- 考虑put的实现流程,若key存在,流程与get的流程基本一致,多了一步更新value;若key不存在,则需要插入新值,首先需要判断当前容量是否满,满的话,需要先删除一个值,即计数为minFreq值,通过minFreq拿到DoubleLinkedNode,然后删除尾结点(前面有解释尾节点即为最久未使用的结点),从HashMap<key, Node>容器中删掉。同样的,若原来计数器所在链表一个node都没有,需要从HashMap<Freq, DoubleLinkedNode>删掉。依据传入的key,value构建新的node,放入计数器为1的HashMap<Freq, DoubleLinkedNode>和HashMap<key, Node>容器中,minFreq赋值为1
编码
public class LFUCache {
class Node {
private int key;
private int value;
private int frequency;
private Node pre;
private Node next;
public Node() {
this(-1, -1, 0);
}
public Node(int key, int value, int frequency) {
this.key = key;
this.value = value;
this.frequency = frequency;
}
}
// 带有头结点和尾节点的双向链表,方便头部插入和尾部获取
class DoubleLinkedNode {
private Node head;
private Node tail;
private int size;
public DoubleLinkedNode() {
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
size =0;
}
public void insertHead(Node node) {
node.pre = head;
node.next = head.next;
head.next.pre = node;
head.next = node;
size++;
}
public void remove(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
size--;
}
public Node getHead() {
return head.next;
}
public Node getTail() {
return tail.pre;
}
}
private HashMap<Integer, Node> keyMap = new HashMap<>();
private HashMap<Integer, DoubleLinkedNode> freqMap = new HashMap<>();
private int capacity;
private int minFreq;
public LFUCache(int capacity) {
this.capacity = capacity;
this.minFreq = 0;
}
public int get(int key) {
Node node = keyMap.get(key);
if (node == null) {
return -1;
}
int freq = node.frequency;
DoubleLinkedNode oldFreq = freqMap.get(freq);
oldFreq.remove(node);
if (oldFreq.size == 0) {
freqMap.remove(freq);
if (minFreq == freq) {
minFreq++;
}
}
node.frequency = freq + 1;
DoubleLinkedNode newFreq = freqMap.getOrDefault(freq + 1, new DoubleLinkedNode());
newFreq.insertHead(node);
freqMap.put(freq+1, newFreq);
return node.value;
}
public void put(int key, int value) {
if (0 >= capacity) {
return;
}
Node node = keyMap.get(key);
if (null == node) {
if (capacity == keyMap.size()) {
DoubleLinkedNode minFreqList = freqMap.get(minFreq);
Node tail = minFreqList.getTail();
minFreqList.remove(tail);
if (minFreqList.size == 0) {
freqMap.remove(minFreq);
}
keyMap.remove(tail.key);
}
node = new Node(key, value, 1);
keyMap.put(key, node);
DoubleLinkedNode newFreq = freqMap.getOrDefault(1, new DoubleLinkedNode());
newFreq.insertHead(node);
freqMap.put(1, newFreq);
minFreq = 1;
} else {
node.value = value;
int freq = node.frequency;
DoubleLinkedNode oldFreq = freqMap.get(freq);
oldFreq.remove(node);
if (oldFreq.size == 0) {
freqMap.remove(freq);
if (minFreq == freq) {
minFreq++;
}
}
node.frequency = freq + 1;
DoubleLinkedNode newFreq = freqMap.getOrDefault(freq + 1, new DoubleLinkedNode());
newFreq.insertHead(node);
freqMap.put(freq+1, newFreq);
}
}
}
测试
LFUCache lfu = new LFUCache(2);
lfu.put(1,1);
lfu.put(2,2);
System.out.println(lfu.get(1)); // 1
lfu.put(3,3);
System.out.println(lfu.get(2)); // -1
System.out.println(lfu.get(3));// 3
lfu.put(4,4);
System.out.println(lfu.get(1));// -1
System.out.println(lfu.get(3));// 3
System.out.println(lfu.get(4));// 4