146. LRU缓存机制
1.题目描述
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
2.思路
LRU 算法实际上是设计数据结构:首先要接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1。
/* 缓存容量为 2 */
LRUCache cache = new LRUCache(2);
// 你可以把 cache 理解成一个队列
// 假设左边是队头,右边是队尾
// 最近使用的排在队头,久未使用的排在队尾
// 圆括号表示键值对 (key, val)
cache.put(1, 1);
// cache = [(1, 1)]
cache.put(2, 2);
// cache = [(2, 2), (1, 1)]
cache.get(1); // 返回 1
// cache = [(1, 1), (2, 2)]
// 解释:因为最近访问了键 1,所以提前至队头
// 返回键 1 对应的值 1
cache.put(3, 3);
// cache = [(3, 3), (1, 1)]
// 解释:缓存容量已满,需要删除内容空出位置
// 优先删除久未使用的数据,也就是队尾的数据
// 然后把新的数据插入队头
cache.get(2); // 返回 -1 (未找到)
// cache = [(3, 3), (1, 1)]
// 解释:cache 中不存在键为 2 的数据
cache.put(1, 4);
// cache = [(1, 4), (3, 3)]
// 解释:键 1 已存在,把原始值 1 覆盖为 4
// 不要忘了也要将键值对提前到队头
分析上面的操作过程,要让 put 和 get 方法的时间复杂度为 O(1),我们可以总结出 cache 这个数据结构必要的条件:查找快,插入快,删除快,有顺序之分。
因为显然 cache 必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。
那么,什么数据结构同时符合上述条件呢?哈希表查找快,但是数据无固定顺序;链表有顺序之分,插入删除快,但是查找慢。所以结合一下,形成一种新的数据结构:哈希链表。
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。这个数据结构长这样:
3.代码
class LRUCache {
public:
LRUCache(int capacity) {
this->cap = capacity;
}
int get(int key) {
auto iter = m.find(key);
if(iter == m.end()){//访问的key不存在
return -1;
}
//key存在,把key-value换到链表的头部
pair<int,int> kv = *m[key];
cache.erase(m[key]);
cache.push_front(kv);
//更新key-value在链表中的位置
m[key] = cache.begin();
return kv.second;
}
void put(int key, int value) {
//判断key是否已经存在
auto iter = m.find(key);
if(iter == m.end()){//key不存在
//判断chace是否已经满了
if(cache.size() == cap){//chace已满
//删除链表尾部的元素
auto lastPair = cache.back();
int lastKey = lastPair.first;
m.erase(lastKey);//删除map中的对应元素
cache.pop_back();
}
//chace没满,直接添加
cache.push_front(make_pair(key,value));
m[key] = cache.begin();
}
else{//key已经存在
//更改value,并放到队列的头部
cache.erase(m[key]);
cache.push_front(make_pair(key,value));
m[key] = cache.begin();
}
}
private:
int cap;//缓存的容量
list<pair<int,int>> cache;//双向链表
unordered_map<int,list<pair<int,int>>::iterator> m;//保存每个key-value在双向链表中的位置
};
/**
* 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);
*/
4.复杂度分析
时间复杂度:O(1)
空间复杂度:O(cap)