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,再来解决此题!

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

 

posted @ 2020-10-27 10:07  junlancer  阅读(193)  评论(0编辑  收藏  举报