链表专题(九):应用篇的无冕之王——「LRU 缓存机制」

链表专题(九):应用篇的无冕之王——「LRU 缓存机制」

场景想象:

你有一个书架(缓存),容量有限(比如只能放 3 本书)。

规则是 “最近最少使用 (Least Recently Used)” 淘汰:

  1. 读取:如果你读了一本书,它就变得“新鲜”了,要把它抽出来放到最前面
  2. 插入
    • 如果书架没满,新书直接插到最前面
    • 如果书架满了,必须把最后面那本(最久没人看的那本)扔掉,然后把新书插到最前面

技术难点:

我们需要两个操作:get(查找)和 put(插入/更新)。

题目要求这两个操作的时间复杂度必须都是 $O(1)$。

  • 只用数组? 插入/删除慢($O(N)$)。
  • 只用链表? 查找慢($O(N)$)。
  • 只用对象/Map? 没办法维护“谁是最新的、谁是最旧的”这种顺序。

终极方案:哈希表 + 双向链表

  • 哈希表 (Map):负责 $O(1)$ 找到某个节点。
  • 双向链表 (Double Linked List):负责 $O(1)$ 移动节点位置(因为双向链表删除节点不需要遍历找前驱)。

力扣 146. LRU 缓存

https://leetcode.cn/problems/lru-cache/

题目分析:

  • 输入capacity (容量)。
  • 实现
    • get(key): 如果存在,返回 value,并把它移到最新位置;不存在返回 -1。
    • put(key, value): 如果 key 存在,更新 value 并移到最新;如果不存在,插入新节点(可能需要淘汰最老的)。

核心思维:虚头虚尾 + Map 映射

为了操作方便,我们给双向链表加上 Dummy HeadDummy Tail

  • Head 右边:永远是最新的数据。
  • Tail 左边:永远是最旧的数据(淘汰候补)。

代码实现 (JavaScript)

这是一道工程题,代码量稍大,但逻辑非常模块化。

JavaScript

// 1. 定义双向链表节点 class Node { constructor(key, value) { this.key = key; this.value = value; this.prev = null; this.next = null; } } // 2. LRU 缓存类 class LRUCache { /** * @param {number} capacity */ constructor(capacity) { this.capacity = capacity; this.map = new Map(); // key -> Node // 创建虚拟头尾节点 this.head = new Node(0, 0); this.tail = new Node(0, 0); // 连起来:head <-> tail this.head.next = this.tail; this.tail.prev = this.head; // 当前节点数量 this.size = 0; } /** * @param {number} key * @return {number} */ get(key) { if (!this.map.has(key)) { return -1; } // 取出节点 const node = this.map.get(key); // 【关键】既然读了它,它就变“新”了,必须移动到链表头部 this.moveToHead(node); return node.value; } /** * @param {number} key * @param {number} value * @return {void} */ put(key, value) { if (this.map.has(key)) { // 情况 1:key 已存在,修改值,并移到头部 const node = this.map.get(key); node.value = value; this.moveToHead(node); } else { // 情况 2:key 不存在,新建节点 const newNode = new Node(key, value); this.map.set(key, newNode); this.addToHead(newNode); // 放到头部 this.size++; // 检查是否超容 if (this.size > this.capacity) { // 淘汰最久未使用的(也就是 tail 前面那个) const removedNode = this.removeTail(); this.map.delete(removedNode.key); // 别忘了从 Map 里也删掉 this.size--; } } } // --- 内部辅助函数 (这就是链表操作的基本功) --- // 1. 将节点添加到头部 (head 之后) addToHead(node) { node.prev = this.head; node.next = this.head.next; this.head.next.prev = node; this.head.next = node; } // 2. 删除一个节点 (双向链表删除 O(1)) removeNode(node) { node.prev.next = node.next; node.next.prev = node.prev; } // 3. 把一个已有节点移到头部 moveToHead(node) { this.removeNode(node); // 先从原来的位置断开 this.addToHead(node); // 再插到头部 } // 4. 移除尾部节点 (用于淘汰) removeTail() { // tail.prev 才是真正的最后一个有效节点 const node = this.tail.prev; this.removeNode(node); return node; // 返回被删的节点,因为外层还需要它的 key 来删 Map } } 

深度模拟

假设 capacity = 2

  1. put(1, 1): Map {1: Node(1)}. List: Head <-> 1 <-> Tail.
  2. put(2, 2): Map {1: Node(1), 2: Node(2)}. List: Head <-> 2 <-> 1 <-> Tail. (2最新)
  3. get(1): 读了 1。1 变新了,移到头。
    • List: Head <-> 1 <-> 2 <-> Tail.
  4. put(3, 3): 满员了!需要淘汰。
    • 淘汰谁?Tail 前面的那个,也就是 2
    • Map 删除 2。
    • 插入 3。List: Head <-> 3 <-> 1 <-> Tail.
  5. get(2): 此时 Map 里没有 2 了,返回 -1。

Read more

【Spring】原理解析:Spring Boot 自动配置

【Spring】原理解析:Spring Boot 自动配置

目录 1.前言 插播一条消息~ 2.正文 2.1加载bean到容器中 2.1.1 @ComponentScan:主动扫描发现Bean 2.1.2 @Import:灵活导入Bean的“万能钥匙” 2.1.3 自定义注解:封装配置的“快捷方式” 2.2Spring Boot原理分析 2.2.1 @SpringBootApplication组合注解 2.2.2 @EnableAutoConfiguration:自动配置的"总开关" 3.小结 1.前言 作为Java开发者,你是否曾在传统Spring项目中反复编写@ComponentScan注解来指定Bean扫描路径?或者在集成第三方组件时,不得不手动通过@Import导入十几个配置类?

By Ne0inhk
Tomcat下载安装以及配置(详细教程)

Tomcat下载安装以及配置(详细教程)

本文讲的是Java环境 文章目录 * 前言 * 下载及安装Tomcat * 启动Tomcat * 测试Tomcat * 配置Tomcat 环境变量 * IDEA中配置Tomcat * Eclipse中配置Tomcat 前言 提示:这里可以添加本文要记录的大概内容: 今天晚上查看自己原来项目的时候,突然发现运行不了,仔细查看发现是tomcat没配置,但是tomcat在电脑里已经下载过了,只是还没有配置,这篇文章就讲tomcat在电脑与idea中的配置 提示:以下是本篇文章正文内容,下面案例可供参考 下载及安装Tomcat 进入tomcat官网,Tomcat官网 选择需要下载的版本,点击下载 下载路径一定要记住,并且路径中尽量不要有中文 下载后是压缩包 .zip,解压后 tomcat系统各个文件夹目录是什么意义: bin:放置的是Tomcat一些相关的命令,启动的命令(startup)和关闭的命令(shutdown)等等 conf:(configure)配置文件 lib:(library)库,依赖的 jar包 logs:Tomca

By Ne0inhk
Go语言×Kingbase数据库极速打通:Gokb驱动三步实操,让国产数据库连接效率嘎嘎提升!

Go语言×Kingbase数据库极速打通:Gokb驱动三步实操,让国产数据库连接效率嘎嘎提升!

引言 Kingbase 作为国产数据库代表,本文将介绍Go语言通过Gokb驱动连接KingbaseES 数据库的全流程,包含环境配置、连接验证、SQL执行及常见问题处理,从基础连接到高级操作全面掌握这一技术栈。 KingbaseES 数据库【系列篇章】: No.文章地址(点击进入)1电科金仓KingbaseES数据库解析:国产数据库的崛起与技术创新2KingBase数据库迁移利器:KDTS工具深度解析与实战指南3KingBase数据库迁移利器:KDTS工具 MySQL数据迁移到KingbaseES实战4电科金仓KingbaseES V9数据库:国产数据库的自主创新与行业实践深度解析5KingbaseES客户端工具Ksql使用全指南:从安装到高级操作6Spring JDBC与KingbaseES深度集成:构建高性能国产数据库应用实战7深度解析:基于 ODBC连接 KingbaseES 数据库的完整操作与实践8Python驱动Ksycopg2连接和使用Kingbase:国产数据库实战指南 一、环境准备 已安装与驱动对应版本的数据库,且数据库连接可用。 1.1 下载Go

By Ne0inhk
PostgreSQL - 事务的提交、回滚与保存点操作

PostgreSQL - 事务的提交、回滚与保存点操作

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕PostgreSQL这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * PostgreSQL - 事务的提交、回滚与保存点操作 * 什么是数据库事务? * PostgreSQL 事务的基本语法 * 1. 开启事务 * 2. 提交事务(COMMIT) * 3. 回滚事务(ROLLBACK) * 4. 保存点(SAVEPOINT) * 事务生命周期图解 * Java 中如何操作 PostgreSQL 事务? * 准备工作 * 关闭自动提交模式 * 基本事务操作示例 * 保存点(SAVEPOINT)的 Java 实现 * 场景:批量插入订单项 * 事务隔离级别详解 * Java 设置隔离级别示例 * 事务与锁机制 *

By Ne0inhk