LeetCode LRU 缓存机制(146题)

LeetCode LRU 缓存机制(146题)

@author:Jingdai
@date:2020.12.11

题目描述(146题)

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存。
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

思路及代码

这个题目需要各个操作的时间复杂度为 O(1) ,需要仔细思考用什么数据结构。

首先是 key,如果直接用链表或数组,寻找 key 就需要 O(n),显然不行。要想 O(1) 找到一个值,基本就是哈希表了,所以肯定是用哈希表来存贮 key

接下来是值,如果直接用类似 HashMap 那样存,我们就无法知道哪一个是最久未使用的数据。所以值的数据肯定是有序的,可以用数组和链表。由于我们需要知道哪一个是最久没有使用的数据,所以每次操作肯定需要移动数据,而数组移动数据的复杂度是 O(n) ,所以就只剩下链表了。对于单链表,删除和移动一个节点需要找到其前驱节点,而单链表得到其前驱节点又要遍历,复杂度就变成了 O(n),故理所当然的是使用双链表。

综上,基本的数据结构就是哈希表加双链表。

接下来看实现思路:

  • 对于链表的操作,为了不对首尾节点特殊处理,一般会加一个不存贮值的头节点和尾结点,方便代码编写,不需要对首尾节点进行特殊判断。

  • get操作:这里将最近使用的节点放在头节点的后一个,最久没有使用的节点放在尾结点的前一个。当 get 操作没有值时,直接返回 -1;如果有值,先将其对应的节点移动到头结点后一个,再返回其值。

  • put操作:如果 key 已有,将对应节点的值进行更改并将对应的节点移动到头结点后;

    如果 key 没有值且容量没有到达上限,新建这个节点并添加到头节点后;

    如果 key 没有值且容量到达上限,新建这个节点并添加到头节点后,然后删除尾结点的前一个节点。

实现过程中会发现一个问题,当需要删除最久未使用的节点时,可以直接删除双链表的尾结点,但是同时还要删除哈希表中的 key,这时就会出现问题,我们无法知道 key 是什么,所以存贮值的双链表节点不仅需要存贮值,还要存贮它的 key,这样删除节点时可以删除其在哈希表中的 key

最后的代码如下:

class DoubleLinkedNode {
    
    
    int key;
    int value;
    DoubleLinkedNode prev;
    DoubleLinkedNode next;

    DoubleLinkedNode(){
    
    }

    DoubleLinkedNode(int key, int value) {
    
    
        this.key = key;
        this.value = value;
    }
} 

private DoubleLinkedNode head;
private DoubleLinkedNode tail;
private int size;
private int capacity;
private HashMap<Integer, DoubleLinkedNode> cache;

public LRUCache(int capacity) {
    
    
    head = new DoubleLinkedNode();
    tail = new DoubleLinkedNode();
    cache = new HashMap<>();
    head.next = tail;
    tail.prev = head;
    this.capacity = capacity;
    this.size = 0;
}

// head recently used
public int get(int key) {
    
    
    if (cache.containsKey(key)) {
    
    
        DoubleLinkedNode node = cache.get(key);
        // take out node
        node.prev.next = node.next;
        node.next.prev = node.prev;
        // put it behind the head
        node.next = head.next;
        node.prev = head;
        head.next = node;
        node.next.prev = node;

        return node.value;
    }
    return -1;
}

public void put(int key, int value) {
    
    
    DoubleLinkedNode node = cache.get(key);
    if (node != null) {
    
    
        node.value = value;
        // take out node
        node.prev.next = node.next;
        node.next.prev = node.prev;
        // put it behind the head
        node.next = head.next;
        node.prev = head;
        head.next = node;
        node.next.prev = node;
    } else {
    
    
        if (this.size < capacity) {
    
    
            node = new DoubleLinkedNode(key, value);
            cache.put(key, node);
            // put it behind the head
            node.next = head.next;
            node.next.prev = node;
            head.next = node;
            node.prev = head;
            size ++;
        } else if (this.size == capacity && capacity != 0){
    
    
            // delete the last
            cache.remove(tail.prev.key);
            tail.prev.prev.next = tail;
            tail.prev = tail.prev.prev;

            node = new DoubleLinkedNode(key, value);
            cache.put(key, node);
            // put it behind the head
            node.next = head.next;
            node.next.prev = node;
            head.next = node;
            node.prev = head;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41512783/article/details/111054414