LRU 缓存双向链表实现

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

  • 设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。

  • 它应该支持以下操作: 获取数据 g e t get 和 写入数据 p u t put

  • 获取数据 g e t ( k e y ) get(key) - 如果密钥 ( k e y ) (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。

  • 写入数据 p u t ( k e y , v a l u e ) put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

  • 示例

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
复制代码

思路分析

  • 这道题的题目描述容易让人产生误解,更常用的语言表达应该是当缓存将要超出最大容量时,删除最久没有使用的 k e y key
  • 当链表或者数组储存对应的 k e y key 时,要遵从一头用于插入 k e y key ,另一头用于删除 k e y key 。类似于先进先出数据结构。因为设计频繁的出入和删除,我们要选链表而不选数组。
  • 对于 put, 当插入的 k e y key 不存在链表中时,直接在链表尾部插入,若链表此时容量大于最大限制容量,则删除链表头部一个结点即可;当插入的 k e y key 已经存在链表中时,先删除链表中对应 k e y key 的结点,再在尾部插入结点。
  • 对于 get, 若 k e y key 不存在链表中直接返回 -1 ,不用对链表进行处理;若 k e y key 存在链表中,先将链表中对应 k e y key 的结点删除,然后将其加入链表尾部。
  • 对于删除链表中的结点,可以用哈希表记录 k e y key 对应的结点,然后进行删除处理。

AC 代码

class LRUCache {

    class Node {
        private int key;
        private int value;
        private Node next;
        private Node pre;
        public Node() {}
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    Map<Integer,Node> map = new HashMap<>();
    Node head;
    Node tail;

    int capacity;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.pre = head;
    }

    private void add(int key, int value) {
        Node node = new Node(key, value);
        tail.pre.next = node;
        node.pre = tail.pre;
        node.next = tail;
        tail.pre = node;
        map.put(key,node);
    }

    private void remove() {
        Node t = head.next;
        head.next = head.next.next;
        head.next.pre = head;
        t.next = null;
        t.pre = null;
        map.remove(t.key);
    }

    private int remove(int key) {
        Node node = map.get(key);
        Node p = node.pre;
        Node n = node.next;
        p.next = n;
        n.pre = p;
        node.next = null;
        node.pre = null;
        return node.value;
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        int value = remove(key);
        add(key,value);
        return value;
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            remove(key);
            add(key, value);
        } else {
            if (map.size() == capacity) {
                remove();
            }
            add(key, value);
        }
    }
}
复制代码

总结

  • 本题非常适合拿双向链表来练手,动动小手多多益善!

猜你喜欢

转载自juejin.im/post/7076414297223462919