LRU 设计最近最少使用缓存

LRU 设计最近最少使用缓存

题目描述

描述
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能

  1. set(key, value):将记录(key, value)插入该结构
  2. get(key):返回key对应的value值

提示:

  1. 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的,然后都会刷新缓存。
  2. 当缓存的大小超过K时,移除最不经常使用的记录。
  3. 输入一个二维数组与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,输出一个答案
  1. 为了方便区分缓存里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),然后改变优先级
    • 缓存空间满了,那么需要替换掉那个优先级最差的元素,这是问题的核心
    1. 考虑使用HashMap存储数据,value使用node节点,node节点串连起来的链表来表示优先级
    2. 每次需要把新操作的节点放在头部,表示其优先级最高;
    3. 尾部的节点优先级最差,需要被从链表中移除;
  • 因此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;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值