LeetCode 30 days Challenge - Day 24
本系列将对LeetCode新推出的30天算法挑战进行总结记录,旨在记录学习成果、方便未来查阅,同时望为广大网友提供帮助。
LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get
and put
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
The cache is initialized with a positive capacity.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
Solution
题目要求分析:要求设计一个缓存机制,并实现访问get操作、put插入操作。
get操作
:当元素不存在,返回-1
。put操作
:当占满空间后再次插入内容时,将最近最少使用的元素删去,替换之。
解法:
本题的朴素解法较简单,本文介绍一种O(1)访存的设计:双向链表(带头尾指针)+ 哈希结构(键值->链表结点)
先分析,该设计如何实现O(1)的访存要求:
-
get操作:按照键值访问哈希表是O(1)的,找到哈希表对应的元素后,访问结点的val元素即可。
-
put操作:
-
检测是否已存在该键值(unordered_map的find函数实现如下,是O(1)的)。
_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits>:: find(const key_type& __k) const -> const_iterator { __hash_code __code = this->_M_hash_code(__k); std::size_t __n = _M_bucket_index(__k, __code); __node_type* __p = _M_find_node(__n, __k, __code); return __p ? const_iterator(__p) : end();
-
检测缓存是否已满是O(1)的。
-
在哈希表中删除(erase)是O(1)的。
-
在链表尾部删除结点、头部插入结点也是O(1)的。
-
综上,能保证是O(1)的访存。
接下来介绍实现中需要注意的几点:
- 删除LRU数据时依据是:通过双向链表尾结点的前一个结点,确定哈希表应该删除哪一表项。因此,链表结点除了记录数据之外,还要记录键值。
m.erase(m.find(rear->pre->key)); // 哈希表的删除语句
- put操作需要进行的判断有三种情况,勿遗漏:
- 缓存已满,插入的键值不存在:删除LRU,在头部插入新键值对(结点)。
- 插入的键值已存在,此时不需考虑缓存是否已满:查找键值对应数据,更新为新值,并移动到头部。
- 插入的键值不存在,缓存未满的情况:直接在同步插入新键值对(结点)。
class LRUCache {
public:
// 双向链表结点定义
struct ListNode {
int key;
int val;
ListNode* pre;
ListNode* next;
ListNode(int x, int y) : key(x), val(y), pre(NULL), next(NULL) {}
};
int cap; // 容量
ListNode* head = new ListNode(0, 0); // 头指针
ListNode* rear = new ListNode(0, 0); // 尾指针
unordered_map<int, ListNode*> m; // 哈希表
// 构造函数
LRUCache(int capacity) {
cap = capacity;
head->next = rear;
rear->pre = head;
}
// 移动结点到头部
void move2front(ListNode* node) {
node->next->pre = node->pre;
node->pre->next = node->next;
node->next = head->next;
node->pre = head;
head->next->pre = node;
head->next = node;
}
// 删除LRU
void removeLRU() {
ListNode* tmp = rear->pre;
rear->pre = tmp->pre;
tmp->pre->next = rear;
delete tmp;
}
// 在头部插入新结点
ListNode* insertNode(int key, int val) {
ListNode* tmp = new ListNode(key, val);
tmp->next = head->next;
tmp->pre = head;
head->next->pre = tmp;
head->next = tmp;
return head->next;
}
// get 访问操作
int get(int key) {
if (m.find(key) != m.end()) {
move2front(m[key]);
return m[key]->val;
}
else {
return -1;
}
}
// put 插入键值对
void put(int key, int value) {
if (m.find(key) == m.end() && m.size() == cap) {
m.erase(m.find(rear->pre->key));
removeLRU();
m[key] = insertNode(key, value);
}
else if (m.find(key) != m.end()) {
m[key]->val = value;
move2front(m[key]);
}
else {
m[key] = insertNode(key, value);
}
}
};
传送门:LRU Cache
2020/4 Karl