数据结构设计之LRU缓存

LeetCode原题

 146. LRU 缓存【中等】,链接: link

解题思路

  • 思路:采用哈希表(查找快,但是数据无固定顺序)+双向链表(有顺序之分,插入删除快,但是查找慢)组合,保证操作的时间复杂度 O(1)。
    • 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
    • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

代码

Java

class LRUCache {
    //思路1:利用JDK自带的LinkedHashMap
    // LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<Integer, Integer>();
    // int capacity;

    // public LRUCache(int capacity) {
    //     this.capacity = capacity;
    // }
    
    // public int get(int key) {
    //     //调用时,更新该key的缓存顺序
    //     if (!cache.containsKey(key)) {
    //         return -1;
    //     }
    //     makeRecently(key);
    //     return cache.get(key);
    // }
    
    // public void put(int key, int value) {
    //     //若已存在
    //     if (cache.containsKey(key)) {
    //         cache.put(key, value);
    //         makeRecently(key);
    //         return;
    //     }
    //     //若不存在,判断是否触发淘汰策略
    //     if (cache.size() >= this.capacity) {
    //         int oldestKey = cache.keySet().iterator().next();
    //         cache.remove(oldestKey);
    //     }
    //     cache.put(key, value);
    // }

    // public void makeRecently(int key) {
    //     int value = cache.get(key);
    //     cache.remove(key);
    //     cache.put(key, value);
    // }

    //思路2:哈希表+手写实现双向链表
    
    //3.实现LRUCache缓存类,包括封装API
    private HashMap<Integer, Node> map;
    private DoubleList cache;
    private int capacity;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        cache = new DoubleList();
    }

    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        //将某个key提升为最近使用的
        Node x = map.get(key);
        cache.remove(x);
        cache.addLast(x);
        return x.val;
    }

    public void put(int key, int val) {
        Node newNode = new Node(key, val);
        if (map.containsKey(key)) {
            // 先删除原节点
            Node oldNode = map.get(key);
            cache.remove(oldNode);
            map.remove(key);
            // 再添加最新节点
            cache.addLast(newNode);
            map.put(key, newNode);
            return;
        }
        if (cache.size() >= this.capacity) {
            // 删除最久未使用的节点
            Node Oldest = cache.removeFirst();
            map.remove(Oldest.key);
        }
        cache.addLast(newNode);
        map.put(key, newNode);
    }

    //1.构建双向链表的节点类
    class Node {
        public int key;
        public int val;
        public Node prev;
        public Node next;
        public Node(int k, int v) {
            this.key = k;
            this.val = v;
        }
    }

    // 2.构建双向链表的API
    class DoubleList {
        // 头尾虚节点
        private Node head;
        private Node tail;
        // 链表元素数量
        private int size;

        public DoubleList() {
            head = new Node(0,0);
            tail = new Node(0,0);
            head.next = tail;
            tail.prev = head;
            size = 0;
        }

        //在链表尾部添加节点x,时间O(1)
        public void addLast(Node x) {
            x.prev = tail.prev;
            x.next = tail;
            tail.prev.next = x;
            tail.prev = x;
            size++;
        }

        //删除链表中的x节点(x一定存在),时间O(1)
        public void remove(Node x) {
            x.prev.next = x.next;
            x.next.prev = x.prev;
            size--;
        }

        //删除链表中的第一个节点,并返回该节点,时间O(1)
        public Node removeFirst() {
            if (head.next == tail) {
                return null;
            }
            Node first = head.next;
            remove(first);
            return first;
        }

        // 返回链表长度,时间O(1)
        public int size() {
            return size;
        }
    }
}

注意要点

  • 细节
    • 在面试中,面试官一般会期望面试者能够自己实现一个简单的双向链表,而不是使用语言自带的、封装好的数据结构。
    • 为什么必须要用双向链表,而不是单链表?因为删除一个节点不光要得到该节点本身的指针,也需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 O(1)。
    • 由于我们要同时维护一个双链表 cache 和一个哈希表 map,很容易漏掉一些操作,比如说删除某个 key 时,在 cache 中删除了对应的 Node,但是却忘记在 map 中删除 key。
  • 延伸
    • LRU(Least Recently Used)算法是按照访问顺序来淘汰缓存,也有按照访问频率进行淘汰缓存的LFU算法。

推荐相关

  • 460 LFU 缓存
  • 380 O(1) 时间插入、删除和获取随机元素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值