1.题目描述
一个缓存结构需要实现如下功能。
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;
如果调用次数最少的key有多个,上次调用发生最早的key被删除
这就是LFU缓存替换算法。实现这个结构,K作为参数给出
数据范围:0<k≤1050<k≤105,∣val∣≤2×109∣val∣≤2×109
要求:get和set的时间复杂度都是 O(logn)O(logn),空间复杂度是 O(n)O(n)
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,返回一个答案
示例1
输入:
[[1,1,1],[1,2,2],[1,3,2],[1,2,4],[1,3,5],[2,2],[1,4,4],[2,1]],3
返回值:
[4,-1]
说明:
在执行"1 4 4"后,"1 1 1"被删除。因此第二次询问的答案为-1
备注:
1≤k≤n≤1051≤k≤n≤105 −2×109≤x,y≤2×109−2×109≤x,y≤2×109
2.解题思路
本题是BM100设计LRU缓存结构的升级版,LFU不仅要判断哪个结点是最久没被使用,还需要多加一个访问频率freq,如果当前页面中已经有k个元素,再put新元素进来时,就需要按照置换规则选择出一个访问频率最低,且最久没被使用过的结点删除,然后再将新元素加入。
因此在设计数据结构时,需要使用一个nodeMap存储key-node对,表示此时已经put进来的结点个数,可以在O(1)的时间获取当前key是否存在;
还需要一个freqMap存储freq-DeLinkedList对,其中freq为每个结点的访问频率,DeLinkedList为一个双向链表,用于记录访问频率为freq的所有结点,最近访问过的结点放在表尾,最久没被访问过的放在表头,还需要一个minFreq记录当前最小的访问频率是多少,便于在nodeMap大小为k时,以O(1)的复杂度快速删除掉需要被置换出去的结点;
本题的注意点有以下几个:
1.在向freqMap中加入当问频率为freq的结点时,如果当前freqMap中不存在key为当前freq的结点,需要先put进一个初始化为两个哨兵结点的双向链表,然后再加入;
2.先弹出访问频率为freq的结点,每次弹出都要判断是否 freq == minFreq && freqMap[freq].size == 0,如果是,要更新minFreq,令其+=1;然后freq+=1,再把这个结点加入到freq+1的双向链表中;
3.如果put的结点key已经存在,那么nodeMap只需要更新val,freq,无需弹出,只有nodeMap.size == k时,才需要从nodeMap中弹出结点,因为nodeMap只是记录某一个结点是否存在,大部分的delete,add都是在双向链表中进行的
4.需要在双向链表中删除和插入结点的几种情况:(1).get(key)已存在,先删除再向频率+1的链表插入;(2).put(key,val)已存在,先删除源频率双向链表中的结点,再向频率+1的链表插入;(3).put(key,val)不存在,看nodeMap().size==k吗,如果是先置换出对应结点,再新建一个节点,put进去;否则直接新建一个节点put进去即可。
3.代码实现
import java.util.*;
class LNode {
public int key;
public int val;
public int freq;
public LNode prev;
public LNode next;
public LNode(int key, int val) {
this.key = key;
this.val = val;
this.freq = 1;
this.prev = null;
this.next = null;
}
}
/**
* 频率-双向链表 freqMap中对应的双向链表结构
*/
class DeLinkedList {
int size;
LNode head = null;
LNode tail = null;
public DeLinkedList() {
size = 0;
head = new LNode(-1, -1);
tail = new LNode(-1, -1);
head.next = tail;
tail.prev = head;
}
public void add(LNode node) {
LNode pre = tail.prev;
pre.next = node;
node.prev = pre;
node.next = tail;
tail.prev = node;
size += 1;
}
public LNode delete (LNode node) {
LNode pre = node.prev;
LNode next = node.next;
next.prev = pre;
pre.next = next;
size -= 1;
return node;
}
}
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* lfu design
* @param operators int整型二维数组 ops
* @param k int整型 the k
* @return int整型一维数组
*/
public Map<Integer, LNode> nodeMap = new HashMap<>();
//存放出现频率 和 这个频率中所有的元素构成的双向链表
public Map<Integer, DeLinkedList> freqMap = new HashMap<>();
public int minFreq = 1;
public int[] LFU (int[][] operators, int k) {
// write code here
List<Integer> res = new ArrayList<>();
for (int i = 0; i < operators.length; i++) {
if (operators[i][0] == 1) {
put(operators[i][1], operators[i][2], k);
} else {
res.add(get(operators[i][1]));
}
}
return res.stream().mapToInt(Integer::intValue).toArray();
}
public int get(int key) {
//如果当前key存在于nodeMap,到其中去找
if (nodeMap.containsKey(key)) {
LNode node = nodeMap.get(key);
//从源频率双向链表中删除 并在新频率双向链表中加入
DeLinkedList oriList = freqMap.get(node.freq);
oriList.delete(node);
if (minFreq == node.freq && oriList.size == 0) {
minFreq += 1;
}
node.freq += 1;
if (!freqMap.containsKey(node.freq)) {
freqMap.put(node.freq, new DeLinkedList());
}
DeLinkedList curList = freqMap.get(node.freq);
curList.add(node);
return node.val;
} else {
return -1;
}
}
public void put(int key, int val, int k) {
if (nodeMap.containsKey(key)) {
LNode node = nodeMap.get(key);
DeLinkedList oriList = freqMap.get(node.freq);
oriList.delete(node);
if (minFreq == node.freq && oriList.size == 0) {
minFreq += 1;
}
node.val = val;
node.freq += 1;
nodeMap.put(key, node);
if (!freqMap.containsKey(node.freq)) {
freqMap.put(node.freq, new DeLinkedList());
}
DeLinkedList curList = freqMap.get(node.freq);
curList.add(node);
} else {
if (nodeMap.size() == k) {
//删除最小出现频率链表中最不经常被访问的结点
DeLinkedList minFreqList = freqMap.get(minFreq);
LNode deleted = minFreqList.delete(minFreqList.head.next);
if (minFreq == deleted.freq && minFreqList.size == 0) {
minFreq += 1;
}
nodeMap.remove(deleted.key, deleted);
}
LNode cur = new LNode(key, val);
nodeMap.put(key, cur);
if (!freqMap.containsKey(1)) {
freqMap.put(1, new DeLinkedList());
}
freqMap.get(1).add(cur);
if (minFreq > 1) {
minFreq = 1;
}
}
}
}