设计LRU缓存结构

背景

最近面了一个公司, 有几道关于最近最少使用的选择题,由于没有明白LRU的含义,导致挂了。今天又看到了这道编程题,尝试自己做,做不出来。后来看了B站的一个视频,照着做了一下,做出来了。但花的时间远远超过了题目给定的时间。结论是,如果下次面试遇到同样的编程题,大概率还是挂。对于我这种反映慢的人,已经不适合这种面试了。工作中遇到同样的问题,大概率是可以解决的,无非时间比别人多花一点。但面试中遇到,没有提前学过或者练过,完全靠自己的设计能力做出来,只能膜拜这类人物了。

最后申明一下:下面的笔记来源于B站:就海海海波吧 账号的视频内容。但视频内容里面的代码不全,我自己补全了,厚着脸皮把文章标记为原创了。如果有冒犯原作者的地方,留言我会删除。本来不想写博客记录的,只是B站的评论内容长度限制了我添加完整的代码。在此申明。

LRU

LRU 全称是 Least Recently Used, 即最近最久未使用的意思。

关键点

  1. 一个最大容量,两个API
  2. 保证都是O(1)的时间复杂度
  3. 上一次访问的元素排在队列的第一个
/*缓存容量为2*/
LRUCache cache = new LRUCache(2);
// 你可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,很久未使用的排在队尾
cache.put(1, 1);
// cache = [(1, 1)]
cache.put(2, 2);
// cache = [(2, 2), (1, 1)]
cache.get(1);// 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键1, 所以提前至队头
cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释: 缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据, 也就是队尾的数据
// 然后把新的数据插入队头
cache.get(2); // 返回 -1(未找到)
// cache = [(3, 3), (1, 1)]
// 解释: cache 中不存在键为 2 的数据
cache.put(1, 4);
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头

特性:
查找快,插入快
删除快,有顺序之分

Map? 不是有序的呀。

双向链表+散列表。

hash表加双向链表

代码实现

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

public class Solution {
//    public static void main(String[] args) {
//        Solution solution = new Solution();
//
//        // [[1,1,1],[1,1,2],[2,1]],3
//        int[][] operators = new int[3][];
//        operators[0] = new int[] {1, 1, 1};
//        operators[1] = new int[] {1, 1, 2};
//        operators[2] = new int[] {2, 1};
//
////        int[][] operators = new int[6][];
////        operators[0] = new int[] {1, 1, 1};
////        operators[1] = new int[] {1, 2, 2};
////        operators[2] = new int[] {1, 3, 2};
////        operators[3] = new int[] {2, 1};
////        operators[4] = new int[] {1, 4, 4};
////        operators[5] = new int[] {2, 2};
//
//        int k = 3;
//        int[] result = solution.LRU(operators, k);
//        System.out.println(Arrays.toString(result));
//
//    }
    /**
     * lru design
     * @param operators int整型二维数组 the ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    public int[] LRU (int[][] operators, int k) {
        // write code here
        LRUCache lruCache = new LRUCache(k);
        ArrayList<Integer> output = new ArrayList();
        for(int i=0; i<operators.length; i++) {
            int[] operator = operators[i];
            int operateCode = operator[0];
            int operateKey = operator[1];
            // 添加
            if (operateCode == 1) {
               lruCache.put(operateKey, operator[2]);
            } else if (operateCode == 2) { //查询
                int res = lruCache.get(operateKey);
                output.add(res);
            }
        }
        int[] out = new int[output.size()];
        for (int i=0; i<output.size(); i++) {
           out[i] =output.get(i);
        }
        return out;
    }

    class LRUCache {
        private int capacity;
        DoubleList list;
        HashMap<Integer, Node> map;

        public LRUCache(int cap) {
            this.capacity = cap;
            list = new DoubleList();
            map = new HashMap<>();
        }

        /**
         * 没有
         * 有
         * @param key
         * @return
         */
        public int get(int key) {
            if (!map.containsKey(key)) {
                return -1;
            }
            // 先得到对应的value.
            int res = map.get(key).value;
            // 将对应的节点提到队列的头部
            put(key, res);
            return res;
        }

        public void put(int key, int val) {
            Node node = new Node(key, val);
            if (map.containsKey(key)) {
                list.remove(map.get(key));
                list.addFirst(node);
                map.put(key, node);
            } else {
                if (capacity == list.size()) {
                    Node last = list.removeLast();
                    map.remove(last.key);
                }
                list.addFirst(node);
                map.put(key, node);
            }
        }
    }

    class Node {
        public int key;
        public int value;
        public Node next;
        public Node prev;

        public Node(int k, int v) {
            this.key = k;
            this.value = v;
        }
    }

    class DoubleList {
        Node head;
        Node tail;
        int size;

        public void addFirst(Node node) {
            if (null == head && null == tail) {
                head = node;
                tail = node;
                size = 1;
                return;
            }
            node.next = head;
            head.prev = node;
            head = node;
            size = size+1;
        }

        public void remove(Node node) {
            if (node == null) {
                return;
            }
            // 1 <-> 2 <-> 3
            Node previous = node.prev;
            Node next = node.next;
            // 移除头结点
            if (null == previous) {
                head = node.next;
                node.next = null;
                if (head != null) {
                    head.prev = null;
                } else {
                    tail = null;
                }
            } else if (null == next) { // 移除尾结点
                tail = node.prev;
                tail.next = null;
                node.prev = null;
            } else {
                previous.next = next;
                next.prev = previous;
            }
            size = size - 1;
        }

        public Node removeLast() {
            Node res = tail;
            Node tailPrev = tail.prev;
            tailPrev.next = null;
            tail.prev = null;
            tail = tailPrev;
            size = size - 1;
            return res;
        }

        public int size() {
            return size;
        }
    }
}
···
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值