cpp
#include <unordered_map>
#include <list>
using namespace std;
class LRUCache {
public:
// 缓存容量
int capacity;
// 双向链表
// 每个元素存储 {key, value}
// 链表头:最近使用
// 链表尾:最久没使用
list<pair<int, int>> cache;
// 哈希表:
// key -> key 在链表中的位置
unordered_map<int, list<pair<int, int>>::iterator> map;
// 构造函数
LRUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
// 1. key 不存在
if (map.find(key) == map.end()) {
return -1;
}
// 2. 找到 key 对应的链表节点
auto it = map[key];
// 3. 保存 value
int value = it->second;
// 4. 删除原来的节点
cache.erase(it);
// 5. 把它重新放到链表最前面
// 表示这个 key 刚刚被使用了
cache.push_front({key, value});
// 6. 更新 map 中保存的位置
map[key] = cache.begin();
return value;
}
void put(int key, int value) {
// 情况1:key 已经存在
if (map.find(key) != map.end()) {
// 找到原来的位置
auto it = map[key];
// 删除旧节点
cache.erase(it);
// 删除旧的映射
map.erase(key);
}
// 情况2:
// key 不存在,或者上面已经把旧 key 删除了
// 判断缓存是否已经满了
if (cache.size() == capacity) {
// 链表最后一个就是最久没使用的元素
auto last = cache.back();
// last.first 就是要删除的 key
int oldKey = last.first;
// 删除 map 中的数据
map.erase(oldKey);
// 删除链表最后一个节点
cache.pop_back();
}
// 把新的 key-value 放在链表最前面
cache.push_front({key, value});
// 保存 key 对应的链表位置
map[key] = cache.begin();
}
};
这道 LRU 题,最值得记的就是下面这些高频点和易错点。
list<pair<int,int>> cache:链表里存的是(key, value),链表头表示最近使用,链表尾表示最久没使用。unordered_map<int, list<pair<int,int>>::iterator> map:map的 key 是缓存 key,value 是这个 key 在链表中的位置,也就是迭代器。map[key] = cache.begin();:把当前 key 和链表头节点建立对应关系。auto it = map[key];:取出这个 key 对应的链表迭代器。it->first:链表节点里的 key。it->second:链表节点里的 value。cache.back():拿到链表最后一个元素本身,所以用last.first、last.second。prev(cache.end()):拿到最后一个元素的迭代器,所以用it->first、it->second。cache.end():不是最后一个元素,而是最后一个元素后面的位置,不能直接访问。cache.erase(it):删除迭代器it指向的节点。cache.push_front({key,value}):把元素放到链表头,表示"刚刚使用过"。cache.pop_back():删除链表尾部,也就是最久没使用的元素。map.find(key) == map.end():key 不存在。map.find(key) != map.end():key 存在。this->capacity = capacity:左边是类成员变量,右边是构造函数参数;同名时用this->区分。
这题最容易错的地方是:删除链表节点后,原来的迭代器失效,所以必须重新更新 map[key]。 例如:
cache.erase(it);
cache.push_front({key, value});
map[key] = cache.begin();
不能只移动链表而不更新 map[key]。
还有一个高频点是,容量满时要同时删两处:
int oldKey = cache.back().first;
map.erase(oldKey); // 删除哈希表中的记录
cache.pop_back(); // 删除链表节点
如果只删链表,不删 map,哈希表里就会留下一个失效迭代器。
最后,把整题逻辑压缩成一句话:
map负责 O(1) 找节点,list负责维护最近使用顺序;访问过的放头部,容量满就删尾部。
时间复杂度也是高频考点:
get:O(1)
put:O(1)
空间复杂度:O(capacity)
你现在刷这题时,最该重点记住的 6 个操作就是:
map.find(key)
map[key]
cache.begin()
cache.erase(it)
cache.push_front(...)
cache.pop_back()
这几个基本就是 LRU 的核心。