• LRU缓存机制


    最近刷题遇到这个问题,甚是喜欢,便想着将自己整个思考的过程拿出来分享一下!

    题目:

    运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

    获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
    写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

    进阶:

    你是否可以在 O(1) 时间复杂度内完成这两种操作?

    示例:

    LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

    cache.put(1, 1);
    cache.put(2, 2);
    cache.get(1); // 返回 1
    cache.put(3, 3); // 该操作会使得关键字 2 作废
    cache.get(2); // 返回 -1 (未找到)
    cache.put(4, 4); // 该操作会使得关键字 1 作废
    cache.get(1); // 返回 -1 (未找到)
    cache.get(3); // 返回 3
    cache.get(4); // 返回 4

    思考过程:

    若是不在乎时间复杂度的话,实现的方案有很多种,我们就直接去思考如何O(1)的时间复杂度,大家很快便能想到读取和插入操作O(1)的HashMap,但是题目要求最近最少使用,什么意思呢,就是你读取和插入操作都会使当前数据排在首位,超过容量,末位即会被淘汰,这边大家会联想到链表来实现这个O(1)级别的数据插入和删除操作,但是链表读取某一个数据是O(n),那么将其整体连起来就是实现一个链表,这个链表的读取,和插入,删除的时间复杂度都是O(1)!下面贴代码,里面有较详细的注释:

    class LRUCache {
        class Node {
            int k;// key
            int v;// value
            int a;// ahead 前面节点的key
            int n;// next 后面节点的key
    
            Node(int k, int v, int a, int n) {
                this.k = k;
                this.v = v;
                this.a = a;
                this.n = n;
            }
        }
    
        int size;// 当前容量-1
        int c;// 最大容量
        int nul = Integer.MIN_VALUE;// 其实这边用包装类型好一点,那么上面就都用包装类型,方便表示null
        Node last; // 末节点
        Node cur;// 头节点
        HashMap<Integer, Node> kn = new HashMap<>();// key和节点的映射关系
    
        public LRUCache(int c) {
            this.c = c;
            size = 0;
        }
    
        public int get(int k) {
            if (kn.containsKey(k)) {// 当存在
                Node node = kn.get(k);
                if (k != cur.k) {// 不为头
                    Node a = kn.get(node.a);
                    if (k != last.k) { //不为尾
                        Node n = kn.get(node.n);
                        a.n = n.k;
                        n.a = a.k;
                    } else {
                        a.n = nul;
                        last = a;
                    }
                    cur.a = node.k;
                    node.n = cur.k;
                    cur = node; //置为头
                    return node.v;
                } else { //为头直接返回
                    return node.v;
                }
            } else {
                return -1;
            }
        }
    
        public void put(int k, int v) {
            if (c == 0) // 当容量c设置为0的时候,其实不存在,可忽略校验
                return;
            if (last == null) { // 当末节点为空时,即空表
                last = new Node(k, v, nul, nul);
                kn.put(k, last);
                size++;
                cur = last;
            } else {
                if (!kn.containsKey(k)) {// 当不存在此key
                    Node node = new Node(k, v, nul, cur.k);// 创建节点,并设置为头节点cur,并进行a,n的处理
                    cur.a = node.k;
                    node.n = cur.k;
                    cur = node;
                    if (size < c) {
                        size++; // 还有空余容量的时候,当前大小+1
                    } else { // 否则将最后一个移除,逻辑和实际都要移除掉
                        int lk = last.k;
                        last = kn.get(last.a);
                        kn.remove(lk);// 实际移除
                        cur = node;
                        if (last != null) {// 注意小心空指针
                            last.n = nul;
                        }
                    }
                    kn.put(k, cur);// 实际添加
                } else {
                    if (k != cur.k) {// 当存在key,则需要在逻辑上设置其为cur,并更新value
                        Node node = kn.get(k);
                        node.v = v;
                        Node a = kn.get(node.a);
                        if (k != last.k) {// 不是末节点
                            Node n = kn.get(node.n);
                            a.n = n.k;
                            n.a = a.k;
                        } else {
                            a.n = nul;
                            last = a;
                        }
                        cur.a = node.k;
                        node.n = cur.k;
                        cur = node;// 置为头
                    } else {
                        cur.v = v;// 恰好为首位,则只跟新value
                    }
                }
            }
    
        }
    }

    其实后面我了解到这个就是Java里面的LinkedHashMap,可以直接调用这个里面的API直接实现所需要求!大家感兴趣可以去了解一下。

    总结:

    之前做题,老是觉得没啥实际意义,感觉只是锻炼思维,而模拟出很多不现实的场景,虽然也可以用其中的思路解决日常所遇到的问题,但做起题目来,有点点枯燥,不过能思考出来还是很开心的,这次的这个变赋予了实际意思,感觉题目就很有意思,看评论区的最高赞也是如此(希望leetCode多一些这样的题),解答此题需要一定的数据结构知识,尤其是链表和哈希表,整个过程其实就是实现链表的操作,搭配上哈希表的操作,建议动手先去实现一个链表的增删改查,而不是单纯调用API,再来解决此题!

    新人小菜鸟,也希望大家见谅!一起前行!

  • 相关阅读:
    VScode快捷键:单行注释和多行注释
    常见状态码的含义
    2019年10月22日 文件操作复习
    2019年10月7日 函数复习
    2019年10月4日 元类
    2019年10月2日 property补充
    2019年10月1日 实现延迟计算功能
    2019年9月30日 property流程分析
    2019年9月29日 自定制property
    2019年9月23日 类的装饰器的应用
  • 原文地址:https://www.cnblogs.com/junlancer/p/13883316.html
Copyright © 2020-2023  润新知