设计LRU缓存结构

1 题目链接

设计LRU缓存结构

2 题目要求

描述

设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能

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

要求

  1. set和get方法的时间复杂度为O(1)
  2. 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
  3. 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案

示例

输入:[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
返回值:[1,-1]
说明:
第一次操作后:最常使用的记录为("1", 1)
第二次操作后:最常使用的记录为("2", 2),("1", 1)变为最不常用的
第三次操作后:最常使用的记录为("3", 2),("1", 1)还是最不常用的
第四次操作后:最常用的记录为("1", 1),("2", 2)变为最不常用的
第五次操作后:大小超过了3,所以移除此时最不常使用的记录("2", 2),加入记录("4", 4),并且为最常使用的记录,然后("3", 2)变为最不常使用的记录

3 代码思路

读完题目之后,注意到set和get的时间复杂度都为O(1),因此首先想到的是使用Map结构。接着,LRU的读取和写入操作都需要将元素设置为最常使用,其中写入操作当元素个数大于等于容量k时,还需要将最不经常使用的元素从图中移除。因此,我们想到需要将数组按照使用时间的先后顺序排序。结合上述两点:Map结构 + 时间有序,想到使用 LinkedHashMap 来解决这道题。

遍历所给的二维数组,根据每个数组的第一个字段判断是set还是get。如果是set,需要判断元素总数和容量k的关系,容量小于k时,将元素加入map,大于等于k时,移除链表中的第一个元素(即最不常用的元素)。如果是get,map中存在key时,将其对应的值返回,并把元素从map中删除,重新加入链表。否则返回-1。

4 代码实现

public class Solution {
    public int[] LRU (int[][] operators, int k) {
        Map<Integer,Integer>map = new LinkedHashMap();
        List<Integer>list = new ArrayList(); // 在这里并不知道一共执行多少次get,使用列表代替数组存储get的值。
        for(int i = 0;i < operators.length; i++){
            int opt = operators[i][0]; 
            int key = operators[i][1]; 
            switch(opt){ // opt==1,执行set;opt==2,执行get
                case 1:
                    if(map.size() >= k){ // 判断元素总数是否超过容量K
                        Iterator it = map.keySet().iterator();
                        map.remove(it.next()); // 移除第一个元素
                    }
                    map.put(key,operators[i][2]);
                    break;
                case 2:
                    if(map.containsKey(key)){ // 判断map是否有当前key,存在则返回,不存在则返回-1
                        int val = map.get(key);
                        list.add(val);
                        map.remove(key);
                        map.put(key,val);
                    }else{
                        list.add(-1);
                    }
                    break;
            }
        }
        int []result = new int[list.size()];
        for(int i = 0; i < list.size(); i++){ // 遍历列表,存入数组
            result[i] = list.get(i);
        }
        return result;
    }
}

5 知识扩充

什么是LRU?

LRU英文全称Least Recently Used,可以理解为最久没有使用。在这种策略下我们用最近一次使用的时间来衡量一块内存的价值,越久之前使用的价值也就越低,最近刚刚使用过的,后面接着会用到的概率也就越大,那么自然也就价值越高。

当然只有这个限制是不够的,我们前面也说了,由于内存是非常金贵的,导致我们可以存储在缓存当中的数据是有限的。比如说我们固定只能存储1w条,当内存满了之后,缓存每插入一条新数据,都要抛弃一条最长没有使用的旧数据。这样我们就保证了缓存当中的数据的价值都比较高,并且内存不会超过限制。

我们把上面的内容整理一下,可以得到几点要求:

  1. 保证缓存的读写效率,比如读写的复杂度都是O(1)
  2. 当一条缓存当中的数据被读取,将它最近使用的时间更新
  3. 当插入一条新数据的时候,弹出更新时间最远的数据

LRU原理

我们仔细想一下这个问题会发现好像没有那么简单,显然我们不能通过数组来实现这个缓存。因为数组的查询速度是很慢的,不可能做到O(1)。其次我们用 HashMap 好像也不行,因为虽然查询的速度可以做到O(1),但是我们没办法做到更新最近使用的时间,并且快速找出最远更新的数据。

如果是在面试当中被问到想到这里的时候,可能很多人都已经束手无策了。但是先别着急,我们冷静下来想想会发现问题其实并没有那么模糊。首先 HashMap是一定要用的,因为只有 HashMap 才可以做到O(1)时间内的读写,其他的数据结构几乎都不可行。但是只有 HashMap 解决不了更新以及淘汰的问题,必须要配合其他数据结构进行。这个数据结构需要能够做到快速地插入和删除,其实我这么一说已经很明显了,只有一个数据结构可以做到,就是链表。

链表有一个问题是我们想要查询链表当中的某一个节点需要O(n)的时间,这也是我们无法接受的。但这个问题并非无法解决,实际上解决也很简单,我们只需要把链表当中的节点作为 HashMap 中的value进行储存即可,最后得到的系统架构如下:

算法-LRU-0

原文链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木水先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值