题目描述
请你设计并实现一个满足 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),这表明一定要用哈希,但是我们在set时,还要存储数据,map<int,pair<int,int>>???--可是我们容量超过限制时还需要pop数据,那么我又该如何判断该pop掉哪个数据?莫非是使用队列?每一次pop最前面的元素吗?那么队列中的每一个元素的数据类型是什么样子的才能保证O(1)的时间复杂度呢?
数据结构
看到答案使用的数据结构时恍然大悟,hashmap+链表.
初始化
我们首先设置一个元素类型为pair<int,int>的链表结构_lru,再设置一个map<int,list<pair<int,int>>::iterator>的hash数据结构_table,这样就能保证对于每一个key值,我们可以得到一个指向list节点的指针.当然,最后还需要一个全局变量保存容量_capacity.
put函数
我们需要先判断这个key值是否是存在的,如果存在,那么我们在更新完_lru链表相应节点的value值后还需要将相应节点移动到_lru链表头,一共两步然后return,但是如果是不存在的,那么我们需要先给_lru链表设置一个新的头结点,然后再令_table[key]=_lru.begin(),指向这个头结点.如果当前_lru.size>capacity,那么我们就需要在_table中erase(_lru.back().first),再删除_lru的back节点(直接pop_back),但是注意二者的顺序是不可以改变的.
set函数
set函数其实比put函数简单一些,当map中找到了key值后,重置其value值,然后移动链表相应节点到链表头结点即可,最后返回相应节点的second即可.但是如果没有找到,就说明找不到当前节点,我们直接返回-1即可.
注意事项
1)容量是_lru的数量,只有当容量大于capacity时,才执行_lru.pop_back() 操作
2)_table类型是unordered_map<int,list::iterator>类型(中间省略了参数),而不是unordered_map<int,list>类型,所以我们_table.find()返回的结果是一个pair<int,list::iterator>类型(记为iter),那么我们在取或者在设置的过程中操作的其实是iter->second (这是一个pair<key,value>类型的结构),在get的过程中return iter->second->second,在set的过程中也是对iter->second->second进行赋值.
代码
#include<unordered_map>
#include<list>
#include<utility>
using namespace std;
class LRUCache {
public:
LRUCache(int capacity) {
_capacity = capacity;
}
int get(int key) {
if (_table.find(key) != _table.end()) {
auto i = _table.find(key); //i是 pair<int,list<pair<int, int>>::iterator>::iterator类型
_lru.splice(_lru.begin(), _lru, i->second);
return i->second->second;
}
return -1;
}
void put(int key, int value) {
auto i = _table.find(key);
if (i != _table.end()) {
_lru.splice(_lru.begin(), _lru, i->second);
i->second->second = value;
return;
}
_lru.emplace_front( key,value );
_table[key] = _lru.begin();
if (_lru.size() > _capacity) {
_table.erase(_lru.back().first);
_lru.pop_back();
}
}
private:
int _capacity ;
list<pair<int, int>> _lru;
unordered_map<int, list<pair<int, int>>::iterator> _table;
};
/**
* 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);
*/