zoukankan      html  css  js  c++  java
  • 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,再来解决此题!

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

  • 相关阅读:
    jmeter(46) redis
    jmeter(45) tcp/ip协议
    Codeforces Round #538 (Div. 2)D(区间DP,思维)
    Codeforces Global Round 1D(DP,思维)
    Educational Codeforces Round 57D(DP,思维)
    UPC11073(DP,思维)
    Yahoo Progamming Contest 2019D(DP,思维)
    Atcoder Beginner Contest 118D(DP,完全背包,贪心)
    Xuzhou Winter Camp 1C(模拟)
    Educational Codeforces Round 57 (Rated for Div. 2)D(动态规划)
  • 原文地址:https://www.cnblogs.com/junalncer/p/13883316.html
Copyright © 2011-2022 走看看