题面
请你设计并实现一个满足 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 <= 30000 <= key <= 100000 <= value <= 105最多调用2 105次get和put
分析
-
要想在 时间内运行 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; } };