LRU 缓存

题面

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

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

函数 get 和 put 必须以 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 <= 10000
  • 0 <= value <= 105 最多调用 2 105 次 get 和 put

分析

  • 要想在 O(1)O(1) 时间内运行 get 函数,那么需要使用哈希表来维护每个 key 对应的 value

  • 要想保存最久未使用的状态,可以使用链表,头节点即为需要逐出的节点

  • 因此需要处理的情况为

    • 当运行 get 函数时,将 key 对应的节点移至尾部
    • 当运行 put 函数时
      • 如果 key 对应的节点不存在,那么新建一个节点,然后将其加入到尾部
      • 如果 key 对应的节点已经存在,那么直接将其移动到尾部
      • 如果加入新节点后,缓存容量不够,那么需要移除头节点
  • 两个关键函数

    • 从链表中摘掉某个节点(不一定释放内存)
    • 在链表末尾加入一个新节点
    • 那么将已经存在的节点移至尾部就变成两个操作,先摘掉该节点,然后在尾部加入该节点
  • 代码实现

    
    struct DListNode {
        int key;
        int val;
        DListNode* pre;
        DListNode* next;
        DListNode() : val(0), pre(nullptr), next(nullptr) {}
        DListNode(int key, int val)
            : key(key), val(val), pre(nullptr), next(nullptr) {}
        DListNode(int key, int val, DListNode* pre, DListNode* next)
            : key(key), val(val), pre(pre), next(next) {}
    };
    class LRUCache {
    private:
        std::unordered_map<int, DListNode*> key2Node;
        DListNode* head = nullptr;
        DListNode* tail = nullptr;
        int capacity;
    
    public:
        LRUCache(int _c) { this->capacity = _c; }
    
        void removeNode(DListNode* node) {
            if (node == nullptr)
                return;
    
            if (node->pre != nullptr) {
                node->pre->next = node->next;
            } else {
                head = node->next;
            }
    
            if (node->next != nullptr) {
                node->next->pre = node->pre;
            } else {
                tail = node->pre;
            }
    
            node->pre = nullptr;
            node->next = nullptr;
        }
    
        void addToTail(DListNode* node) {
            if (node == nullptr)
                return;
            if (tail == nullptr) {
                head = tail = node;
            } else {
                node->pre = tail;
                tail->next = node;
                tail = node;
            }
        }
    
        int get(int key) {
            if (key2Node.find(key) == key2Node.end())
                return -1;
            DListNode* res = key2Node[key];
            removeNode(res);
            addToTail(res);
            return res->val;
        }
    
        void put(int key, int value) {
            if (key2Node.find(key) == key2Node.end()) {
                DListNode* node = new DListNode(key, value);
                key2Node[key] = node;
                addToTail(node);
                if (key2Node.size() > capacity) {
                    DListNode* toRemove = head;
                    key2Node.erase(toRemove->key);
                    removeNode(toRemove);
                    delete toRemove;
                }
            } else {
                DListNode* node = key2Node[key];
                node->val = value;
                removeNode(node);
                addToTail(node);
            }
        }
    
        ~LRUCache() {
            DListNode* curr = head;
            while (curr != nullptr) {
                DListNode* next = curr->next;
                delete curr;
                curr = next;
            }
            key2Node.clear();
        }
    };
    
    /**
     * Your LRUCache object will be instantiated and called as such:
     * LRUCache* obj = new LRUCache(capacity);
     * int param_1 = obj->get(key);
     * obj->put(key,value);
     */
  • 下一步优化:将 head 和 tail 改造成「一个 dummy 哨兵节点的循环双向链表」,这样可以消除所有边界判断

    • 只有一个 dummy
    • dummy->next 是最久未使用
    • dummy->pre 是最近使用
    • 空链表时:dummy->next = dummy,dummy->pre = dummy
  • 代码实现

    #include <unordered_map>
    
    struct DListNode {
        int key;
        int val;
        DListNode* pre;
        DListNode* next;
    
        DListNode() : key(0), val(0), pre(nullptr), next(nullptr) {}
        DListNode(int k, int v) : key(k), val(v), pre(nullptr), next(nullptr) {}
    };
    
    class LRUCache {
    private:
        std::unordered_map<int, DListNode*> key2Node;
        DListNode* dummy;
        int capacity;
    
        void removeNode(DListNode* node) {
            node->pre->next = node->next;
            node->next->pre = node->pre;
        }
    
        void addToTail(DListNode* node) {
            node->pre = dummy->pre;
            node->next = dummy;
            dummy->pre->next = node;
            dummy->pre = node;
        }
    
    public:
        LRUCache(int _capacity) : capacity(_capacity) {
            dummy = new DListNode();
            dummy->next = dummy;
            dummy->pre = dummy;
        }
    
        int get(int key) {
            auto it = key2Node.find(key);
            if (it == key2Node.end())
                return -1;
    
            DListNode* node = it->second;
            removeNode(node);
            addToTail(node);
            return node->val;
        }
    
        void put(int key, int value) {
            auto it = key2Node.find(key);
    
            if (it == key2Node.end()) {
                DListNode* node = new DListNode(key, value);
                key2Node[key] = node;
                addToTail(node);
    
                if (key2Node.size() > capacity) {
                    DListNode* toRemove = dummy->next;
                    key2Node.erase(toRemove->key);
                    removeNode(toRemove);
                    delete toRemove;
                }
            } else {
                DListNode* node = it->second;
                node->val = value;
                removeNode(node);
                addToTail(node);
            }
        }
    
        ~LRUCache() {
            DListNode* curr = dummy->next;
            while (curr != dummy) {
                DListNode* next = curr->next;
                delete curr;
                curr = next;
            }
            delete dummy;
        }
    };
这篇文档有帮助吗?

On this page

页面信息