news 2026/6/10 14:39:37

LeetCode 460 - LFU 缓存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 460 - LFU 缓存


文章目录

    • 摘要
    • 描述
    • 题解答案
    • 题解代码分析
      • 节点需要存什么信息?
      • 为什么要用「频次 → 双向链表」?
      • LFUCache 的核心结构
      • get 操作怎么做?
      • put 操作的关键点
      • 更新频次是整个设计的核心
    • 示例测试及结果
    • 与实际场景结合
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

LFU 缓存是缓存算法里的“进阶关卡”。

LRU 大家都很熟,但 LFU 往往是很多人刷 LeetCode 时第一次真正感受到:
“原来 O(1) 的设计不是写得快,而是数据结构选得对。”

这道题不只是让你写一个缓存,而是在逼你回答三个工程级问题:

  1. 怎么在 O(1) 内找到最少使用的 key?
  2. 使用次数相同时,怎么再按 LRU 淘汰?
  3. get / put 都要是 O(1),不能靠排序、遍历硬撑。

描述

题目要求你实现一个 LFUCache,支持:

  • get(key)

    • 存在:返回 value,并且使用次数 +1
    • 不存在:返回 -1
  • put(key, value)

    • key 已存在:更新 value,并且使用次数 +1

    • key 不存在:

      • 如果缓存没满,直接插

      • 如果缓存满了:

        • 先淘汰使用次数最少
        • 如果次数相同,淘汰最久没用的

并且有一个非常重要的硬指标:

getput的平均时间复杂度必须是O(1)

这直接排除了所有“排序 / 每次扫描一遍”的方案。

题解答案

真正能跑通并满足 O(1) 的解法,核心是三层结构:

  1. key → Node 映射
  2. freq → 双向链表 映射
  3. 一个全局的minFreq

一句话总结:

用 HashMap 快速定位节点,用「频次分桶 + LRU 链表」来控制淘汰顺序。

题解代码分析

节点需要存什么信息?

每一个缓存项,至少要知道:

  • key
  • value
  • 当前使用频率 freq
  • 在链表里的前后指针(为了 O(1) 删除)
classNode{letkey:Intvarvalue:Intvarfreq:Int=1varprev:Node?varnext:Node?init(_key:Int,_value:Int){self.key=keyself.value=value}}

为什么要用「频次 → 双向链表」?

因为 LFU 有一个隐藏条件:

同一个频次下,要按 LRU 淘汰

所以每个频次桶,本质上是一个LRU 链表

classDoublyLinkedList{lethead=Node(0,0)lettail=Node(0,0)varsize=0init(){head.next=tail tail.prev=head}funcaddToHead(_node:Node){node.next=head.next node.prev=head head.next?.prev=node head.next=node size+=1}funcremove(_node:Node){node.prev?.next=node.next node.next?.prev=node.prev size-=1}funcremoveLast()->Node?{guardsize>0,letlast=tail.prev,last!==headelse{returnnil}remove(last)returnlast}}

LFUCache 的核心结构

classLFUCache{privateletcapacity:IntprivatevarminFreq=0privatevarkeyToNode=[Int:Node]()privatevarfreqToList=[Int:DoublyLinkedList]()
  • keyToNode:O(1) 找节点
  • freqToList:O(1) 找某个频次的 LRU 链表
  • minFreq:O(1) 知道该淘汰谁

get 操作怎么做?

核心逻辑只有三步:

  1. 查 key 是否存在
  2. 从旧 freq 链表中移除
  3. freq +1,放进新链表
funcget(_key:Int)->Int{guardletnode=keyToNode[key]else{return-1}updateFreq(node)returnnode.value}

put 操作的关键点

  • capacity 为 0,直接返回

  • key 已存在:更新 value + 更新 freq

  • key 不存在:

    • 如果满了:

      • minFreq对应的链表里,删最久未使用
    • 插入新节点,freq = 1

funcput(_key:Int,_value:Int){ifcapacity==0{return}ifletnode=keyToNode[key]{node.value=valueupdateFreq(node)return}ifkeyToNode.count==capacity{ifletlist=freqToList[minFreq],letremoved=list.removeLast(){keyToNode.removeValue(forKey:removed.key)}}letnewNode=Node(key,value)keyToNode[key]=newNodeletlist=freqToList[1]??DoublyLinkedList()list.addToHead(newNode)freqToList[1]=list minFreq=1}

更新频次是整个设计的核心

privatefuncupdateFreq(_node:Node){letfreq=node.freqifletlist=freqToList[freq]{list.remove(node)iffreq==minFreq&&list.size==0{minFreq+=1}}node.freq+=1letnewList=freqToList[node.freq]??DoublyLinkedList()newList.addToHead(node)freqToList[node.freq]=newList}

这里做了三件事:

  • 从旧频次链表移除
  • 如果刚好是 minFreq 且链表空了,更新 minFreq
  • 插入新频次链表头部

示例测试及结果

letlfu=LFUCache(2)lfu.put(1,1)lfu.put(2,2)print(lfu.get(1))// 1lfu.put(3,3)print(lfu.get(2))// -1print(lfu.get(3))// 3lfu.put(4,4)print(lfu.get(1))// -1print(lfu.get(3))// 3print(lfu.get(4))// 4

输出结果:

1 -1 3 -1 3 4

和题目示例完全一致。

与实际场景结合

LFU 在真实项目里比你想象中常见,比如:

  • CDN 本地缓存
  • App 内图片 / 数据缓存
  • 后端热点数据保护
  • 推荐系统中的候选集缓存

相比 LRU,LFU 更适合:

“长期高频访问的数据,不能因为短期冷却就被干掉”

比如用户主页、配置数据、热门商品列表。

时间复杂度

  • get:O(1)
  • put:O(1)

这是通过HashMap + 双向链表 + minFreq联合保证的。

空间复杂度

O(capacity)

所有节点、链表总数都和缓存容量线性相关。

总结

LFU 这道题,真正难的不是代码多,而是:

  • 你能不能拆清楚“频次”和“时间”这两个维度
  • 你能不能在 O(1) 下把这两个维度同时维护住

如果你能完整写出这道题,其实已经具备了:

  • 设计复杂缓存系统的能力
  • 面试中讲清楚“为什么这样设计”的底气
  • 把算法真正落地成工程代码的经验
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/21 14:39:38

PyTorch-CUDA镜像与CI/CD流水线集成实践

PyTorch-CUDA镜像与CI/CD流水线集成实践 在现代AI研发中,一个常见的痛点是:开发者本地能跑通的模型,在CI环境或生产服务器上却频频报错——CUDA版本不兼容、cuDNN缺失、PyTorch编译选项不对……这类“在我机器上明明没问题”的尴尬场景&#…

作者头像 李华
网站建设 2026/6/5 12:53:01

GEO优化实操指南:从SEO到AI搜索可见性的演进

在AI驱动的搜索生态中,GEO优化(Generative Engine Optimization)是一种专门针对生成式AI引擎(如ChatGPT、Perplexity、Gemini、Google AI Overview等)进行内容优化的策略,其核心目标是让你的内容不仅被索引…

作者头像 李华
网站建设 2026/5/28 19:41:12

Docker Compose部署PyTorch环境?这份教程帮你快速上手

Docker Compose部署PyTorch环境?这份教程帮你快速上手 在深度学习项目开发中,最让人头疼的往往不是模型调参,而是环境配置——“在我机器上能跑”的尴尬局面几乎每个AI工程师都经历过。CUDA版本不匹配、cuDNN缺失、PyTorch与系统驱动冲突………

作者头像 李华
网站建设 2026/5/28 16:44:29

40-智能优化算法-哈里斯鹰算法 该算法有较强的全局搜索能力,并且需要调节的参数较少的优点,可...

40-智能优化算法-哈里斯鹰算法 该算法有较强的全局搜索能力,并且需要调节的参数较少的优点,可修改性极高。优化算法的江湖中总有些后起之秀让人眼前一亮。今天要聊的哈里斯鹰算法(HHO),就像是算法界的特种部队&#xf…

作者头像 李华
网站建设 2026/6/10 1:49:21

PyTorch镜像中处理大型数据集的最佳实践

PyTorch镜像中处理大型数据集的最佳实践 在深度学习项目中,一个常见的困境是:模型代码写好了,却卡在环境配置上——CUDA版本不匹配、cuDNN缺失、PyTorch与驱动不兼容……尤其是面对千万级图像或TB级文本数据时,这些问题会被进一步…

作者头像 李华