Medium

题目描述

设计一个遵循最近最少使用 (LRU) 缓存约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value;如果不存在,则向缓存中插入该组 key-value。如果插入操作导致关键字数量超过 capacity,则应该逐出最近最少使用的关键字

函数 getput 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10^4
  • 0 <= value <= 10^5
  • 最多调用 2 * 10^5getput

解题思路

解题思路

LRU缓存需要同时支持快速查找和维护访问顺序,这需要结合两种数据结构来实现:

核心思想

  1. 哈希表:实现O(1)时间的查找操作
  2. 双向链表:维护元素的访问顺序,支持O(1)时间的插入和删除

设计要点

  • 双向链表的头部表示最近使用的元素,尾部表示最久未使用的元素
  • 哈希表存储key到链表节点的映射关系
  • 每次访问元素时,将其移动到链表头部
  • 当容量满时,删除链表尾部元素

操作流程

get操作

  1. 在哈希表中查找key
  2. 如果存在,将对应节点移动到链表头部,返回值
  3. 如果不存在,返回-1

put操作

  1. 如果key已存在,更新值并移动到头部
  2. 如果key不存在:
    • 创建新节点并插入到头部
    • 如果超过容量,删除尾部节点并从哈希表中移除

这种设计保证了所有操作都能在O(1)时间内完成。

代码实现

class LRUCache {
private:
    struct Node {
        int key, value;
        Node* prev;
        Node* next;
        Node() : key(0), value(0), prev(nullptr), next(nullptr) {}
        Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}
    };
    
    unordered_map<int, Node*> cache;
    Node* head;
    Node* tail;
    int capacity;
    
    void addToHead(Node* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    
    void removeNode(Node* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }
    
    void moveToHead(Node* node) {
        removeNode(node);
        addToHead(node);
    }
    
    Node* removeTail() {
        Node* last = tail->prev;
        removeNode(last);
        return last;
    }
    
public:
    LRUCache(int capacity) {
        this->capacity = capacity;
        head = new Node();
        tail = new Node();
        head->next = tail;
        tail->prev = head;
    }
    
    int get(int key) {
        Node* node = cache[key];
        if (node == nullptr) {
            return -1;
        }
        moveToHead(node);
        return node->value;
    }
    
    void put(int key, int value) {
        Node* node = cache[key];
        if (node == nullptr) {
            Node* newNode = new Node(key, value);
            cache[key] = newNode;
            addToHead(newNode);
            if (cache.size() > capacity) {
                Node* tail = removeTail();
                cache.erase(tail->key);
                delete tail;
            }
        } else {
            node->value = value;
            moveToHead(node);
        }
    }
};
class LRUCache:
    class Node:
        def __init__(self, key=0, value=0):
            self.key = key
            self.value = value
            self.prev = None
            self.next = None

    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = {}
        # 创建虚拟头尾节点
        self.head = self.Node()
        self.tail = self.Node()
        self.head.next = self.tail
        self.tail.prev = self.head

    def add_to_head(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node

    def remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def move_to_head(self, node):
        self.remove_node(node)
        self.add_to_head(node)

    def remove_tail(self):
        last = self.tail.prev
        self.remove_node(last)
        return last

    def get(self, key: int) -> int:
        node = self.cache.get(key)
        if not node:
            return -1
        self.move_to_head(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        node = self.cache.get(key)
        if not node:
            new_node = self.Node(key, value)
            self.cache[key] = new_node
            self.add_to_head(new_node)
            if len(self.cache) > self.capacity:
                tail = self.remove_tail()
                del self.cache[tail.key]
        else:
            node.value = value
            self.move_to_head(node)
public class LRUCache {
    private class Node {
        public int Key { get; set; }
        public int Value { get; set; }
        public Node Prev { get; set; }
        public Node Next { get; set; }
        
        public Node(int key = 0, int value = 0) {
            Key = key;
            Value = value;
        }
    }
    
    private Dictionary<int, Node> cache;
    private Node head;
    private Node tail;
    private int capacity;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        cache = new Dictionary<int, Node>();
        head = new Node();
        tail = new Node();
        head.Next = tail;
        tail.Prev = head;
    }
    
    private void AddToHead(Node node) {
        node.Prev = head;
        node.Next = head.Next;
        head.Next.Prev = node;
        head.Next = node;
    }
    
    private void RemoveNode(Node node) {
        node.Prev.Next = node.Next;
        node.Next.Prev = node.Prev;
    }
    
    private void MoveToHead(Node node) {
        RemoveNode(node);
        AddToHead(node);
    }
    
    private Node RemoveTail() {
        Node last = tail.Prev;
        RemoveNode(last);
        return last;
    }
    
    public int Get(int key) {
        if (cache.TryGetValue(key, out Node node)) {
            MoveToHead(node);
            return node.Value;
        }
        return -1;
    }
    
    public void Put(int key, int value) {
        if (cache.TryGetValue(key, out Node node)) {
            node.Value = value;
            MoveToHead(node);
        } else {
            Node newNode = new Node(key, value);
            cache[key] = newNode;
            AddToHead(newNode);
            if (cache.Count > capacity) {
                Node tail = RemoveTail();
                cache.Remove(tail.Key);
            }
        }
    }
}
var LRUCache = function(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
};

LRUCache.prototype.get = function(key) {
    if (this.cache.has(key)) {
        const value = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, value);
        return value;
    }
    return -1;
};

LRUCache.prototype.put = function(key, value) {
    if (this.cache.has(key)) {
        this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
        const firstKey = this.cache.keys().next().value;
        this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
};

复杂度分析

操作时间复杂度空间复杂度
getO(1)O(capacity)
putO(1)O(capacity)

说明:

  • 时间复杂度:哈希表的查找、插入、删除都是O(1),双向链表的插入、删除也是O(1)
  • 空间复杂度:需要存储最多capacity个节点,每个节点占用常数空间

相关题目