// LFU比LRU更复杂一些
// 具体来说,在get操作时,如果key存在, 则需要频次+1并插入链表头
// 如果key不存在,则直接返回-1
// put操作时, 当key已经存在,与get操作类似,只是把value值进行更新
// 当key不存在时,判断容量大小,是否需要删除key
// 删除的时候,得删除频次最低的那个key
// 当存在多个频次最低的key时, 删除最久未使用的那个
// 整体思路就是一个hashmap存key和key-value的映射,保证取key是O(1)操作
// 还要一个hashmap存频次与key-value的映射,保证在频次操作添加和删除节点是O(1)
// 注意,因为同一个频次可以对应多个key,所以此hashmap的后一项是一个双向链表
// 插入频次在双向链表头部,删除频次在双向链表尾部
// 单独封装一个双向链表, 插入,删除, 删除末尾元素,判空更便于操作
// 同时,还需要一个当前LFU的最低频次,这在插入和删除键的时候得用到
// 例如,刚开始插入一个key,最低频次是1,但是多次get或者put之后,最低频次是会改变的
// 即当cache的容量为1时,get已存在的key时,假设此时key频次为1,需要删除频次为1的hashmap中的对应双向链表
// 还得创建频次为2的hashmap与双向链表映射, 将key插入其中。
class LFUCache {
int minFreq;
int capacity;
HashMap<Integer, ListNode> keyTable;
HashMap<Integer, DoublyLinkedList> freqTable;
public LFUCache(int capacity) {
this.capacity = capacity;
minFreq = 0;
keyTable = new HashMap<>();
freqTable = new HashMap<>();
}
public int get(int key) {
if (!keyTable.containsKey(key)) // 键不存在直接返回
return -1;
ListNode node = keyTable.get(key); // 获取key-value节点
int tmp = node.freq; // 获取key当前频次
node.freq++; // 更新key的频次
//在key所在频次链表中的key
DoublyLinkedList dLink = freqTable.get(tmp); // 获取key同频次的双向链表
dLink.removeNode(node); //将顶点从此频次链表移除
if (dLink.isEmpty()) { // 此频次链表原来只有key一个值,现在为空了
freqTable.remove(tmp); // 删除该频次的映射和双向链表
if (minFreq == tmp) // 如果此时删除的该频次最后一个key就是整个LFU的最低频次
minFreq++; // 表示此时cache中存在的最低频次要随着key进行+1操作
}
// 将key插入到频次+1的对应链表中
tmp++;
if (!freqTable.containsKey(tmp)) { // 注意如果此频次不存在,需要先new一个链表
freqTable.put(tmp, new DoublyLinkedList());
}
freqTable.get(tmp).addNode(node);
return node.val;
}
public void put(int key, int value) {
if (capacity == 0) // 数据中存在容量为0的情况,导致错误
return ;
// key不在cache中
if (!keyTable.containsKey(key)) {
ListNode node = new ListNode(key, value, 1); // 创建新的节点存放key-value
if (keyTable.size() < capacity) { // 未超过cache容量
// if (!freqTable.containsKey(1)) // 不存在频次1的频次与链表映射时 这一步可以省略
// freqTable.put(1, new DoublyLinkedList());
;
}else { // 需要删除频次最低的key
DoublyLinkedList dLink = freqTable.get(minFreq); // 获取频次最低的双向链表
int delKey = dLink.removeTail(); // 从双向链表中删除尾节点
if (dLink.isEmpty()){ // 若删除key导致链表为空
freqTable.remove(minFreq); // 则删除该频次与链表银蛇
minFreq++; // 最低频次必然是更高一次的频次
}
keyTable.remove(delKey); // 在key映射中删除key
}
keyTable.put(key, node); // 添加key映射
if (!freqTable.containsKey(1)) // 注意:可能之前的get操作导致频次为1的映射已经移除了
freqTable.put(1, new DoublyLinkedList());
freqTable.get(1).addNode(node);
minFreq = 1;
}else {
// key在cache中 与get大致相同,只是多了一个更新value的操作
ListNode node = keyTable.get(key);
node.val = value;
int tmp = node.freq;
node.freq++;
DoublyLinkedList dLink = freqTable.get(tmp);
dLink.removeNode(node);
if (dLink.isEmpty()) {
freqTable.remove(tmp);
if (minFreq == tmp)
minFreq++;
}
tmp++;
if (!freqTable.containsKey(tmp)) {
freqTable.put(tmp, new DoublyLinkedList());
}
freqTable.get(tmp).addNode(node);
}
}
}
class ListNode {
int key;
int val;
int freq;
ListNode pre;
ListNode next;
public ListNode() {}
public ListNode(int key, int val, int freq) {
this.key = key;
this.val = val;
this.freq = freq;
pre = null;
next = null;
}
}
class DoublyLinkedList {
ListNode head;
ListNode tail;
public DoublyLinkedList() {
head = new ListNode(); // 两个哨兵方便链表操作
tail = new ListNode();
head.next = tail;
tail.pre = head;
}
public void addNode(ListNode node) { // 添加到头部
node.next = head.next;
node.pre = head;
head.next.pre = node;
head.next = node;
}
public void removeNode(ListNode node) { //删除节点
node.next.pre = node.pre;
node.pre.next = node.next;
}
public int removeTail() { // 移除尾部节点
ListNode node = tail.pre;
removeNode(node);
return node.key;
}
public boolean isEmpty() { // 判断链表是否为空
if (head.next == tail)
return true;
return false;
}
}
Leetcode 460 LFU缓存机制(最低频次最少使用)
最新推荐文章于 2022-09-07 17:53:33 发布