当前位置: 首页 > news >正文

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) 下把这两个维度同时维护住

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

  • 设计复杂缓存系统的能力
  • 面试中讲清楚“为什么这样设计”的底气
  • 把算法真正落地成工程代码的经验
http://www.gsyq.cn/news/177597.html

相关文章:

  • Artix-7 FPGA中双端口BRAM实现技巧操作指南
  • Git fetch 详解:git fetch 和 git fetch origin 到底有什么区别?(origin/xxx、远端跟踪分支一次讲透)
  • 提示工程架构师的成长之路:强化学习优化提示词是必经关卡吗?
  • 不仅是写 Bug:从“愿望谈话” (Wish Conversations) 开始,帮技术人找到 AI 无法替代的“核心影响力”
  • Git 开发全流程:一套不踩坑的 Git 团队开发完整流程(小白教程)
  • 01 风光储并网协同运行 包含永磁风机发电机、光伏阵列、储能系统及其各自控制系统。 永磁直驱风机
  • PyTorch-CUDA-v2.8镜像备份与恢复策略:保障业务连续性
  • git commit频繁报错?统一开发环境从PyTorch镜像开始
  • 亮亮仔筹开防守 财神爷
  • 吴恩达深度学习课程四:计算机视觉 第四周:卷积网络应用 (一) 人脸识别
  • YOLOv5/YOLOv11模型训练提速秘籍:PyTorch-CUDA-v2.8镜像实战
  • 课程设计初步选题
  • 目录
  • 不用再git clone了!PyTorch-CUDA镜像内置完整开发套件
  • 如何自定义扩展PyTorch-CUDA镜像?Dockerfile编写教程
  • diskinfo检测NVMe缓存:优化PyTorch-CUDA-v2.8数据读取速度
  • 共识机制RBFT的具体流程
  • github organization管理团队项目:协作开发PyTorch-CUDA-v2.8
  • 阿里云服务器如何实现与其他阿里云产品的无缝集成?
  • 华为云国际站代理商EDCM主要有什么作用呢?
  • Hyperchain中区块打包的实现
  • anaconda配置pytorch环境耗时太久?建议切换至容器化方案
  • GitHub项目本地复现难?PyTorch-CUDA镜像帮你搞定依赖
  • Java毕设选题推荐:基于springboot的骑行交流论坛的设计与开发基于SpringBoot的在线骑行网站的设计与实现.【附源码、mysql、文档、调试+代码讲解+全bao等】
  • PyTorch-CUDA环境 vs 传统Anaconda:谁更适合深度学习?
  • 【TVM教程】设计与架构
  • jupyter notebook主题美化:提升PyTorch-CUDA-v2.8编码体验
  • GitHub热门项目都在用的PyTorch环境,现在一键就能部署
  • github pages搭建文档站:展示PyTorch-CUDA-v2.8使用文档
  • PyTorch-CUDA-v2.8镜像支持T4/V100/A10?云服务器兼容性一览