LRU 设计最近最少使用缓存
题目描述
描述
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
提示:
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的,然后都会刷新缓存。
- 当缓存的大小超过K时,移除最不经常使用的记录。
- 输入一个二维数组与K,二维数组每一维有2个或者3个数字,第1个数字为opt,第2,3个数字为key,value
- 若opt=1,接下来两个整数key, value,表示set(key, value)
- 若opt=2,接下来一个整数key,表示get(key),若key未出现过或已被移除,则返回-1
- 对于每个opt=2,输出一个答案
- 为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹
进阶:
- 你是否可以在O(1)的时间复杂度完成set和get操作
示例1
- 输入:
[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
- 返回值:
[1,-1]
- 说明:
[1,1,1],第一个1表示opt=1,要set(1,1),即将(1,1)插入缓存,缓存是{“1”=1}
[1,2,2],第一个1表示opt=1,要set(2,2),即将(2,2)插入缓存,缓存是{“1”=1,“2”=2}
[1,3,2],第一个1表示opt=1,要set(3,2),即将(3,2)插入缓存,缓存是{“1”=1,“2”=2,“3”=2}
[2,1],第一个2表示opt=2,要get(1),返回是[1],因为get(1)操作,缓存更新,缓存是{“2”=2,“3”=2,“1”=1}
[1,4,4],第一个1表示opt=1,要set(4,4),即将(4,4)插入缓存,但是缓存已经达到最大容量3,移除最不经常使用的{“2”=2},插入{“4”=4},缓存是{“3”=2,“1”=1,“4”=4}
[2,2],第一个2表示opt=2,要get(2),查找不到,返回是[1,-1]
示例2
- 输入:
[[1,1,1],[1,2,2],[2,1],[1,3,3],[2,2],[1,4,4],[2,1],[2,3],[2,4]],2
- 返回值:
[1,-1,-1,3,4]
方案一:我的笨办法
创建一个数组datas,用来存储数据,长度为k*2,表示缓存大小为k,其中每个数据项存储两个数据,对应key和value;
set:
- 如果数组中已经包含key,则在将该位置的keyvalue变成新的;修改lruList的值,对应位设置为1,其他位值++;
- 如果数组没有满,则在数组末尾添加(key,value);修改lruList的值,对应位设置为1,其他位值++;
- 如果数组满了,查找lrulist,找出优先级最差的位置,将该位置的keyvalue变成新的;修改lruList的值,对应位设置为1,其他位值++;
get:
- 遍历datas,找到数据,找不到则返回-1,找到则返回对应值
- 将被访问的位置的lrelist对应位设置为1,其余位++
import java.util.*;
public class Solution {
int[][] datas; //数据存储位置
int[] lruList; //lru
ArrayList<Integer> array=new ArrayList<>();//结果集
public int[] LRU (int[][] operators, int k) {
int datanum=0; //空间内的数据量
datas=new int[k][2];//k为LRU的缓存区大小,2为每个元素存储2位数据
lruList=new int[k];
for(int i=0;i<operators.length;i++){
if(operators[i][0]==1){
datanum=set(operators[i][1],operators[i][2],k,datanum);
}
if(operators[i][0]==2){
get(operators[i][1],datanum);
}//if
}//for
//ArrayList转化成List
int[] r=new int[array.size()];
for(int n=0;n<array.size();n++){
r[n]=array.get(n);
}
return r;
}
public void get(int key,int datanum){
boolean flag=false;//表示未找到
//读取数据
int j; //表示遍历的索引
for(j=0;j<datanum;j++){
if(datas[j][0]==key){
array.add(datas[j][1]);
flag=true;
break;//为了保存找到的索引j
}
}
if(!flag){
array.add(-1);
}
//优先级设置。该位为1,其他位++
if(flag){
for(int m=0;m<datanum;m++){
if(m==j)
lruList[m]=1;
else
lruList[m]++;
}//for
}//if
}
public int set(int key,int value,int k,int datanum){
//查找key是否已经存在,若已存在,直接替换
//或者已经插满了,找优先级最差的那个来替换
//先把index找到
int index=-1;
//遍历datas 找到相同的key.
for(int j=0;j<datanum;j++){
if(datas[j][0]==key){
index=j;
break;
}
}
//如果没有相同的而且插满了,同样需要找一个替换的
if(index==-1 && datanum==k){
//找出优先级最差的(数值最高的)来替换
int maxlru=lruList[0];
index=0;
for(int m=1;m<k;m++){
if (lruList[m]>maxlru){
maxlru=lruList[m];
index=m;
}
}
}
//现在我们拿着替换的index来替换,将第index位数据,替换为operators读取到的第i位数据
if(index!=-1){
datas[index][0]=key;
datas[index][1]=value;
//设置优先级,其他位优先级+1,该位优先级设置为1
changeLRU(index,datanum); //其他位+1,index位=1
}
if(datanum<k){
//存储数据(尾插)
datas[datanum][0]=key;
datas[datanum][1]=value;
datanum++;
//更改优先级,每一位的优先级都+1,刚刚插进的第datanum位设置为1(原本为0,++变为1)
changeLRU(datanum-1,datanum); //其他位+1,datanum-1位=1
}
return datanum;
}
public void changeLRU(int index1,int datanum){
for(int j=0;j<datanum;j++){
lruList[j]++;
}
lruList[index1]=1;
}
}
方案二:如何使得get和set的时间复杂度都为O(1)
分析问题: LRU的缓存结构,要求set和get操作都要满足O(1)的时间复杂度
get:
- 查询的O(1)需要使用HashMap实现
Set:
-
如果key已经存在,则直接修改value值,然后改变优先级
-
如果key不存在:
- 缓存空间还没满,则直接put(key,value),然后改变优先级
- 缓存空间满了,那么需要替换掉那个优先级最差的元素,这是问题的核心
- 考虑使用HashMap存储数据,value使用node节点,node节点串连起来的链表来表示优先级
- 每次需要把新操作的节点放在头部,表示其优先级最高;
- 尾部的节点优先级最差,需要被从链表中移除;
-
因此node节点需要构建一个双向链表
import java.util.*;
public class Solution {
//需要维护的成员变量
//Map 存储数据
Map<Integer,Node> map=new HashMap<>();
//链表 表示优先级,head和tail分别指向链表头和链表尾(需要注意这里链表的头尾还没有相连)
Node head=new Node(-1,-1);
Node tail=new Node(-1,-1);
public int[] LRU (int[][] operators, int k) {
//设定链表 头尾相连
head.next=tail;
tail.pre=head;
//需要return的结果数组的长度
int len=(int)Arrays.stream(operators).filter(x->x[0]==2).count();
int[] res=new int[len];
int resIndex=0; //res的索引
//遍历输入,对其做get和set分类
for(int i=0;i<operators.length;i++){
if(operators[i][0]==1){
set(operators[i][1],operators[i][2],k);
}
else if(operators[i][0]==2){
res[resIndex++]=get(operators[i][1]);
}
}
return res;
}
public void set(Integer key,Integer value, int k){
if(map.containsKey(key)){
Node node=map.get(key);
//从链表中删除
node.pre.next=node.next;
node.next.pre=node.pre;
//修改value值
node.value=value;
//放在链表头,表示优先级最高
insertAfterHead(node);
}else{
if(map.size()==k){
//删除尾部的节点、元素
int delete_key=tail.pre.key;
tail.pre.pre.next=tail;
tail.pre=tail.pre.pre;
map.remove(delete_key);
}
//无论map.size==k或<key,都需要new节点进链表头,把值放进map中
Node node=new Node(key,value);
insertAfterHead(node);
map.put(key,node);
}
}
public void insertAfterHead(Node node){
node.next=head.next;
head.next.pre=node;
head.next=node;
node.pre=head;
}
public int get(Integer key){
if(map.containsKey(key)){
Node node=map.get(key);
//把节点从原位置删除
node.pre.next=node.next;
node.next.pre=node.pre;
//把节点加入头部
insertAfterHead(node);
return node.value;
}
return -1;
}
static class Node{
int key,value;
Node pre,next;
public Node(int key,int value){
this.key=key;
this.value=value;
}
public Node(int key,int value,Node pre,Node next){
this.key=key;
this.value=value;
this.pre=pre;
this.next=next;
}
}
}