LFU缓存结构设计(最近最少使用)
前言:
因为面试要求当场撸LFU缓存结构设计题面试的时候没有写出来,所以这里做一个缓存算法集合。😭
1.FIFO(先进先出队列)
2.LRU(最近最久未使用)
3.LFU(最近最少使用)
题目:
leetcode_LFU
牛客_LFU
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。
get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。
「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。
要求:
在 O(1) 时间复杂度内执行两项操作
示例:
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
思路:
首先就是要明白什么叫LFU(最近最少使用),和LRU类似,但不同的在于缓存满了删除的时候,LFU是删除 使用次数(频率)最少的元素。tip:不懂区别的的建议先看LRU的那题
1.同样的,根据题目要求O(1)的解法,所以我们需要一个Hash表来进行元素的定位(缓存元素)。Map<Integer,Node> cache
2.然后我们采用多个双向链表,来存储每个频率有那些元素Map<Integer,DLinked> num_map
,将其链接起来,链表head的就是当前频率
中最久未使用的
,tail的就是当前频率
中最近使用的
。
3.我们需要一个min标记,用来标记当前最小的频率是几
删除操作过程:通过min标记得到最小频率, 通过num_map.get(min)得到频率对应的双向链表DLinked,DLinked.head.next就是对应最近最少使用的Node
, 然后进行相关删除操作即可
代码:
class LFUCache {
private Map<Integer,Node> cache;
private Map<Integer,DLinked> num_map;
private Integer size;
private Integer min=0;
class Node{
int key;
int value;
//频率
int num;
Node next;
Node pre;
public Node(int key,int value){
this.key=key;
this.value=value;
num=1;
}
}
class DLinked{
Node head;
Node tail;
public DLinked(){
head=new Node(0,0);
tail=new Node(0,0);
head.next=tail;
tail.pre=head;
}
//从该链表中移除
public void remove(Node node){
node.pre.next=node.next;
node.next.pre=node.pre;
}
//加到链表尾部.pre
public void AddToTail(Node node){
node.next=tail;
node.pre=tail.pre;
tail.pre.next=node;
tail.pre=node;
}
}
//初始化操作
public LFUCache(int capacity) {
size=capacity;
min=0;
cache = new HashMap<>();
num_map=new HashMap<>();
}
public int get(int key) {
Node node = cache.get(key);
//如果缓存中包含该元素
if(node!=null){
//更新该元素的位置即可
num_update(node);
return node.value;
}
return -1;
}
public void put(int key, int value) {
//特判,如果缓存大小为0的话,那么直接return即可
if(size==0){
return ;
}
Node node = cache.get(key);
//如果该元素存在,则更新元素位置即可(频率)
if(node!=null){
node.value=value;
num_update(node);
}else{
//如果元素不存在
//如果缓存满了
if(cache.size()==size){
//需要把最久未使用且频率最低的给删掉
DLinked min_linked=num_map.get(min);
cache.remove(min_linked.head.next.key);
min_linked.remove(min_linked.head.next);
}
Node newNode = new Node(key,value);
cache.put(key,newNode);
DLinked dlinked = num_map.get(1);
if(dlinked==null){
dlinked = new DLinked();
num_map.put(1,dlinked);
}
dlinked.AddToTail(newNode);
min=1;
}
}
//更新元素的位置
private void num_update(Node node){
//获取该元素(频率)所在的链表
DLinked dlinked= num_map.get(node.num);
//将其从该链表中移除
dlinked.remove(node);
//如果当前更新的元素的num(频率)为最小频率,且移除该元素后,此链表中没有元素了,则更新min
if(node.num==min&&dlinked.head.next==dlinked.tail){
min=node.num+1;
}
//需要将其更新到下一个频率的链表中
node.num++;
DLinked nextlinked= num_map.get(node.num);
if(nextlinked==null){
nextlinked = new DLinked();
num_map.put(node.num,nextlinked);
}
nextlinked.AddToTail(node);
}
}