设计LFU缓存结构
LFU:最近最少频率使用
基本思想: 当缓存满时,加入新数据,淘汰缓存中使用次数最少的key,当使用次数最少的key有多个,删除最早调用的key。
定义节点的数据结构
class Node{
//使用频率
int freq;
//key值,用于数据更新的key
int key;
//value值
int val;
public Node(int freq, int key, int val){
this.freq = freq;
this.key = key;
this.val = val;
}
}
确定实现缓存的的数据结构
- 定义一个<频率,链表>的频率集合freq_map
- 定义一个<key, node>的节点集合map
链表存储的是相同使用频率的节点
当执行完[1,1,1],[1,2,2],[1,3,2]操作后,freq_map此时的状况:
频率为 1 的双向链表内有三个节点,按照插入顺序,节点每次都是从头部插入,删除节点每次都是尾部开始,目的是维护节点的调用秩序, 当频率相同时,删除最先使用的节点,即链表尾部的节点。
具体操作
- update
- 先获取节点的使用频率;
- 删除freq_map中双向链表中的对应节点
- 执行2之后,freq_map对应freq的链表可能为空,此时需要删除这个freq节点,注意如果该freq为最小频率,需要更新min_freq, 应为freq已经删除了,min_freq+1.
private void update(Node node ,int key, int val){
//找到频率
int freq = node.freq;
//原频率中删除该节点
freq_map.get(freq).remove(node);
//哈希表中该频率已经无节点,直接删除
if(freq_map.get(freq).isEmpty()){
freq_map.remove(freq);
//若当前频率为最小,最小频率加1
if(min_freq == freq){
min_freq++;
}
}
if(!freq_map.containsKey(freq+1)){
freq_map.put(freq+1, new LinkedList<Node>());
}
//插入频率加1 的双向链表表头,链表中对应: freq key value
freq_map.get(freq+1).addFirst(new Node(freq+1, key,val));
map.put(key,freq_map.get(freq+1).getFirst());
}
- set
插入节点,首先判map中是否存在key:
- 存在,调用update函数,进行数据更新操作
- 不存在,插入新数据,但是插入之前需要判断缓存容量:
1. 剩余空间为0, 即缓存已满,执行淘汰策略
2. 剩余空间不为0,剩余空间-1,先存入ferq_map中,再存入map中。
private void set(int key, int val){
if(map.containsKey(key)){
update(map.get(key),key,val);
}else{
if(size == 0){
int oldkey = freq_map.get(min_freq).getLast().key;
freq_map.get(min_freq).removeLast();
if(freq_map.get(min_freq).isEmpty()){
freq_map.remove(min_freq);
}
//链表哈希表中删除
map.remove(oldkey);
}else{
size--;
}
min_freq = 1;
if(!freq_map.containsKey(1)){
freq_map.put(1,new LinkedList<Node>());
}
freq_map.get(1).addFirst(new Node(1,key,val));
map.put(key, freq_map.get(1).getFirst());
}
}
- get
get操作在获取元素后,需要对freq_map和map进行更新操作。
private int get(int key){
int res = -1;
if(map.containsKey(key)){
Node node = map.get(key);
res = node.val;
update(node,key,res);
}
return res;
}
完整代码
public class Solution {
/**
* lfu design
* @param operators int整型二维数组 ops
* @param k int整型 the k
* @return int整型一维数组
*/
class Node{
int freq;
int key;
int val;
public Node(int freq, int key, int val){
this.freq = freq;
this.key = key;
this.val = val;
}
}
//频率和双向链表的哈希表
private Map<Integer, LinkedList<Node>> freq_map = new HashMap<>();
//key到节点的哈希表
private Map<Integer, Node> map = new HashMap<>();
//记录缓存剩余容量
private int size = 0;
//记录当前最小频率
private int min_freq = 0;
public int[] LFU (int[][] operators, int k) {
// 构建初始化连接
//链表剩余大小
this.size = k;
//获取操作数
int len = (int)Arrays.stream(operators).filter(x->x[0]==2).count();
int[] res = new int[len];
//遍历所有操作
for(int i=0, j=0; i<operators.length;i++){
if(operators[i][0] == 1){
set(operators[i][1],operators[i][2]);
}else{
res[j++] = get(operators[i][1]);
}
}
return res;
}
//调用函数时更新频率或者val值
private void update(Node node ,int key, int val){
//找到频率
int freq = node.freq;
//原频率中删除该节点
freq_map.get(freq).remove(node);
//哈希表中该频率已经无节点,直接删除
if(freq_map.get(freq).isEmpty()){
freq_map.remove(freq);
//若当前频率为最小,最小频率加1
if(min_freq == freq){
min_freq++;
}
}
if(!freq_map.containsKey(freq+1)){
freq_map.put(freq+1, new LinkedList<Node>());
}
//插入频率加1 的双向链表表头,链表中对应: freq key value
freq_map.get(freq+1).addFirst(new Node(freq+1, key,val));
map.put(key,freq_map.get(freq+1).getFirst());
}
private void set(int key, int val){
if(map.containsKey(key)){
update(map.get(key),key,val);
}else{
if(size == 0){
int oldkey = freq_map.get(min_freq).getLast().key;
freq_map.get(min_freq).removeLast();
if(freq_map.get(min_freq).isEmpty()){
freq_map.remove(min_freq);
}
//链表哈希表中删除
map.remove(oldkey);
}else{
size--;
}
min_freq = 1;
if(!freq_map.containsKey(1)){
freq_map.put(1,new LinkedList<Node>());
}
freq_map.get(1).addFirst(new Node(1,key,val));
map.put(key, freq_map.get(1).getFirst());
}
}
private int get(int key){
int res = -1;
if(map.containsKey(key)){
Node node = map.get(key);
res = node.val;
update(node,key,res);
}
return res;
}
}