leetcode 146LRU缓存

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> mapmap 的 key 是缓存 key,value 是这个 key 在链表中的位置,也就是迭代器。
  • map[key] = cache.begin();:把当前 key 和链表头节点建立对应关系。
  • auto it = map[key];:取出这个 key 对应的链表迭代器。
  • it->first:链表节点里的 key。
  • it->second:链表节点里的 value。
  • cache.back():拿到链表最后一个元素本身,所以用 last.firstlast.second
  • prev(cache.end()):拿到最后一个元素的迭代器,所以用 it->firstit->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 的核心。

相关推荐
郝学胜_神的一滴2 小时前
干货版《算法导论》18:遍历增删原理、时间复杂度与集合序列实现全解
数据结构·算法
-dzk-2 小时前
【滑动窗口】LC 3.无重复字符的最长子串
算法·滑动窗口
zzxdear2 小时前
用 OR-Tools CP-SAT 求解最简单的柔性作业车间调度(FJSP)
算法
luj_17682 小时前
变频技术核心原理揭秘
服务器·c语言·开发语言·经验分享·算法
mmsx2 小时前
置灰按钮为什么自己又“亮“了?一次状态缓存“双写冲突“的排查记录
java·缓存·bug·livedata
rannn_1112 小时前
【力扣hot100】图论专题+模板|DFS、BFS、拓扑排序...
java·算法·leetcode·深度优先·图论
_Narcissus_2 小时前
B树概念及操作笔记(含完整代码实现)
c语言·数据结构·数据库·c++·笔记·b树·算法
l1258652 小时前
# RAG多轮对话检索设计:Query重写如何让“那它呢“变成完整问题
前端·数据库·人工智能·python·算法·fastapi·milvus
疯狂打码的少年2 小时前
【数据结构】图的应用:最短路径(Dijkstra / Floyd)
数据结构·笔记·算法