LFU缓存中min_freq必须被动更新而非遍历查找,仅在新key插入(置1)或原频次桶变空且等于min_freq时更新;需用find()避免隐式构造,节点必存freq字段,淘汰与min_freq更新须原子执行。

LFU 缓存要真正达到 O(1) 淘汰,min_freq 不能靠遍历找,必须被动更新;否则每次 evict() 都扫频次桶,直接退化成 O(F) 或更差。
为什么 min_freq 不能每次淘汰前重新扫描
常见错误是:在 put() 触发淘汰时,循环检查 freq_to_list[1]、freq_to_list[2]…直到非空。这在频次稀疏(比如只有 1、5、100 出现过)或高并发下极易超时——LeetCode 460 就会 TLE。
-
freq_to_list若用std::map<int std::list>></int>,查begin()是 O(log F),但遍历找第一个非空桶最坏是 O(F) -
freq_to_list若用std::vector<:list>></:list>,下标越界风险高,且未使用的频次位置永远占内存 - 真正瓶颈不在“查哪个频次最小”,而在“查这个频次有没有节点”——它本该是常数时间判断,却被写成隐式循环
min_freq 的唯一合法更新时机
它只在两个明确事件发生时才变:put 插入新 key(重置为 1),或某次 get/put 导致 freq_to_list[old_freq] 变空且 old_freq == min_freq。
- 新 key 插入:直接
min_freq = 1,不用查——因为freq_to_list[1]此刻必然非空 - 频次提升后原链表为空:仅当
old_freq == min_freq才触发++min_freq;否则min_freq不动(它只会升,不会降) - 淘汰后检查:若
freq_to_list[min_freq].empty(),必须++min_freq并继续检查,直到非空或越界(越界说明缓存刚清空,下次插入会重置)
如何避免 freq_to_list 查找开销影响 min_freq 判断
用 std::unordered_map<int std::list>></int> 存频次桶时,freq_to_list.count(min_freq) 是 O(1),但 freq_to_list[min_freq].empty() 会隐式构造空链表(如果 key 不存在)。必须用 find() 先查是否存在。
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
if (freq_to_list[min_freq].empty())—— 若min_freq不在 map 中,operator[]会默认构造一个空std::list,污染数据且多一次哈希插入 - 正确写法:
auto it = freq_to_list.find(min_freq); if (it != freq_to_list.end() && it->second.empty()) - 更优实践:把
freq_to_list改成std::vector<:list>></:list>+ 动态扩容,用下标直接索引,但需配合max_freq截断(如 >1000 归入高频桶),避免内存爆炸
节点结构里必须带 freq 字段,否则 min_freq 更新失效
只靠链表位置反推频次不可靠——链表可能被复用、裁剪,或节点被移动后未同步更新所属频次。每次 get() 提升频次时,若节点里没存当前 freq,就无法知道该从哪个 freq_to_list 桶里删它。
-
struct Node { int key; int value; int freq; std::list<node>::iterator list_iter; };</node>——freq是刚需,不是冗余 -
list_iter必须在插入新链表后立即更新,且只在该链表内有效;跨链表迁移时,旧list_iter失效,不能复用 - 漏掉
node.freq++或写成++node.freq(顺序无关),会导致后续所有频次操作错位,min_freq完全失准
最常被忽略的点:淘汰逻辑和 min_freq 更新必须原子耦合——evict() 取节点、删节点、删 key_map、检查链表是否空、更新 min_freq,这一串不能拆开,中间插入任何其他操作(比如日志打印或回调)都可能让 min_freq 暂时指向空桶。



















