leetcode hot100【LeetCode 146. LRU缓存】java实现

LeetCode 146. LRU缓存

题目描述

设计和实现一个 LRU (Least Recently Used) 缓存机制。它应该支持以下操作:

  • get(key):如果缓存中存在 key,则返回 value,否则返回 -1
  • put(key, value):如果缓存已满,移除最久未使用的项,然后插入新的 key-value 对。如果 key 已存在,则更新其 value

Java 实现解法

解法:使用哈希表和双向链表
import java.util.HashMap;

// 定义一个 LRU(Least Recently Used,最近最少使用)缓存类
class LRUCache {
    // 定义一个内部节点类,用于表示双向链表中的节点
    private class Node {
        // 存储键
        int key;
        // 存储值
        int value;
        // 指向前一个节点的指针
        Node prev;
        // 指向后一个节点的指针
        Node next;

        // 节点的构造函数,用于初始化键和值
        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    // 使用 HashMap 来存储键和对应的节点,方便快速查找
    private HashMap<Integer, Node> cache;
    // 双向链表的虚拟头节点
    private Node head;
    // 双向链表的虚拟尾节点
    private Node tail;
    // 缓存的最大容量
    private int capacity;

    // 构造函数,初始化缓存的容量
    public LRUCache(int capacity) {
        // 设置缓存的最大容量
        this.capacity = capacity;
        // 初始化存储键值对的 HashMap
        cache = new HashMap<>();
        // 创建虚拟头节点
        head = new Node(0, 0);
        // 创建虚拟尾节点
        tail = new Node(0, 0);
        // 让头节点的下一个节点指向尾节点
        head.next = tail;
        // 让尾节点的前一个节点指向头节点
        tail.prev = head;
    }

    // 向双向链表头部添加一个节点的方法
    private void addNode(Node node) {
        // 让新节点的前一个节点指向头节点
        node.prev = head;
        // 让新节点的下一个节点指向头节点原来的下一个节点
        node.next = head.next;
        // 让头节点原来的下一个节点的前一个节点指向新节点
        head.next.prev = node;
        // 让头节点的下一个节点指向新节点
        head.next = node;
    }

    // 从双向链表中移除一个节点的方法
    private void removeNode(Node node) {
        // 获取要移除节点的前一个节点
        Node prev = node.prev;
        // 获取要移除节点的后一个节点
        Node next = node.next;
        // 让前一个节点的下一个节点指向后一个节点
        prev.next = next;
        // 让后一个节点的前一个节点指向前一个节点
        next.prev = prev;
    }

    // 移除并返回双向链表尾部节点的方法
    private Node popTail() {
        // 获取尾节点的前一个节点(即要移除的节点)
        Node res = tail.prev;
        // 调用移除节点的方法移除该节点
        removeNode(res);
        // 返回被移除的节点
        return res;
    }

    // 根据键获取值的方法
    public int get(int key) {
        // 如果缓存中不包含该键,返回 -1 表示未找到
        if (!cache.containsKey(key)) return -1;
        // 从缓存中获取对应的节点
        Node node = cache.get(key);
        // 先将该节点从双向链表中移除
        removeNode(node);
        // 再将该节点添加到双向链表头部,表示它是最近使用的
        addNode(node);
        // 返回该节点存储的值
        return node.value;
    }

    // 向缓存中放入键值对的方法
    public void put(int key, int value) {
        // 如果缓存中已经包含该键
        if (cache.containsKey(key)) {
            // 从缓存中获取对应的节点
            Node node = cache.get(key);
            // 先将该节点从双向链表中移除
            removeNode(node);
            // 更新该节点存储的值
            node.value = value;
            // 再将该节点添加到双向链表头部,表示它是最近使用的
            addNode(node);
        } else {
            // 创建一个新节点存储键值对
            Node newNode = new Node(key, value);
            // 如果缓存中的元素数量已经达到或超过最大容量
            if (cache.size() >= capacity) {
                // 移除并获取双向链表尾部节点
                Node tail = popTail();
                // 从缓存中移除该尾部节点对应的键值对
                cache.remove(tail.key);
            }
            // 将新节点添加到双向链表头部
            addNode(newNode);
            // 将新节点的键值对存入缓存
            cache.put(key, newNode);
        }
    }
}

解题思路

  1. 使用一个哈希表存储键值对。
  2. 使用双向链表保持元素的使用顺序,头部表示最近使用,尾部表示最久未使用。
  3. 当访问元素时,将其移动到链表的头部;当缓存满时,移除链表尾部元素。
  4. 复杂度分析:时间复杂度:get:O(1) ,put:O(1)
    空间复杂度:O(capacity),存储缓存和链表节点。

你可能感兴趣的:(LeetCode,Hot100,leetcode,缓存,java,链表)