<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.tinystar.me</id>
    <title>Ethkuil's Blog</title>
    <updated>2026-03-07T17:50:46.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Ethkuil</name>
        <email>ethkuil@gmail.com</email>
        <uri>https://www.tinystar.me</uri>
    </author>
    <link rel="alternate" href="https://www.tinystar.me"/>
    <link rel="self" href="https://www.tinystar.me/atom.xml"/>
    <subtitle>弄斧必到班门。</subtitle>
    <rights>All rights reserved 2026, Ethkuil</rights>
    <entry>
        <title type="html"><![CDATA[工程：背景、目标、需求、设计、执行、评估]]></title>
        <id>https://www.tinystar.me/post/10</id>
        <link href="https://www.tinystar.me/post/10"/>
        <updated>2026-03-07T17:50:46.000Z</updated>
        <summary type="html"><![CDATA[初稿创建于2023年9月，不过后续陆续改动很大。 本文是我自用的框架。仅供参考。 概念厘定 「工程」是「改造世界」的过程。组合使用各种工具，实现需求、达成目标1。 流程框架 初始有 背景信息 和 目标。 目标 -> 需求 -> 设计 -> 执行，相邻的2个阶段互为接口与实现。 理想效果：具有“无后效性”，每个阶段只依赖前一个阶段而不依赖更前的阶段，每个阶段完成后都可以取代上一个阶段。当然，现实中难以完全实现，难免需要回溯修改。但这种回溯修改不应被当成是无代价的；尤其在协作中，应记录变更原因并评估影响。 最后再补一个 评估。评估「执行」的最终结果是否足够好地满足了「需求」（不是「目标」）。 工程...]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>初稿创建于2023年9月，不过后续陆续改动很大。</p>
<p>本文是我自用的框架。仅供参考。</p>
</blockquote>
<h2 id="概念厘定" class="group"><a aria-hidden="true" tabindex="-1" href="#概念厘定"><span class="icon icon-link"></span></a>概念厘定</h2>
<p>「工程」是「改造世界」的过程。组合使用各种工具，实现需求、达成目标<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>。</p>
<h2 id="流程框架" class="group"><a aria-hidden="true" tabindex="-1" href="#流程框架"><span class="icon icon-link"></span></a>流程框架</h2>
<p>初始有 <strong>背景信息</strong> 和 <strong>目标</strong>。</p>
<p><strong>目标 -> 需求 -> 设计 -> 执行</strong>，相邻的2个阶段互为接口与实现。</p>
<p>理想效果：具有“无后效性”，每个阶段只依赖前一个阶段而不依赖更前的阶段，每个阶段完成后都可以取代上一个阶段。当然，现实中难以完全实现，难免需要回溯修改。但这种回溯修改不应被当成是无代价的；尤其在协作中，应记录变更原因并评估影响。</p>
<p>最后再补一个 <strong>评估</strong>。评估「执行」的最终结果是否足够好地满足了「需求」（不是「目标」）。</p>
<p>工程经常会循环往复地迭代，评估后可能会回到前面的阶段。</p>
<h3 id="背景信息-background-bg" class="group"><a aria-hidden="true" tabindex="-1" href="#背景信息-background-bg"><span class="icon icon-link"></span></a>背景信息 (background, bg)</h3>
<p>背景信息用于锚定工程的起点。它并非对世界的完整描述，而是筛选后的约束与前提。</p>
<h3 id="目标-mission" class="group"><a aria-hidden="true" tabindex="-1" href="#目标-mission"><span class="icon icon-link"></span></a>目标 (mission)</h3>
<blockquote>
<p>亦称 动机 (motivation).</p>
</blockquote>
<p>目标是驱动整个工程的根本原因。</p>
<p>目标是<strong>发自内心</strong>的，是本质而主观的，无法轻易改变。也因此，通常是<strong>简明扼要</strong>的。</p>
<p>明确目标有利于避免 忘记初心，越搞越歪。<br>
特别地，对于产品来说，明确目标对营销极为重要。</p>
<p>目标<strong>通常不具体，甚至可能不明确，甚至可能不现实</strong>。因此需要好好结合背景信息做需求分析，得到明确、可行的需求。</p>
<h2 id="需求-requirements" class="group"><a aria-hidden="true" tabindex="-1" href="#需求-requirements"><span class="icon icon-link"></span></a>需求 (requirements)</h2>
<p>需求是根据背景信息得到的对目标的特化。如果目标实在不现实，也可能有所妥协，但至少是那个方向。</p>
<blockquote>
<p>必须分清「手段」和「目标」。固然不能教条，但也不能被手段异化、忘记初心。</p>
</blockquote>
<p><strong>「需求」是工程直接要解决的问题</strong>；目标只是导引方向。也因此，「需求」可以“外包”出去，而不需要提供其对应的「目标」。</p>
<p>因此，需求要尽可能<strong>明确</strong>，避免模糊表述。</p>
<h2 id="设计-design" class="group"><a aria-hidden="true" tabindex="-1" href="#设计-design"><span class="icon icon-link"></span></a>设计 (design)</h2>
<p>决策、规划，不断将问题<strong>具体</strong>化，给出可以按部就班执行的方案。</p>
<h2 id="辨析-目标需求设计" class="group"><a aria-hidden="true" tabindex="-1" href="#辨析-目标需求设计"><span class="icon icon-link"></span></a>辨析 目标、需求、设计</h2>
<p>总结目标、需求、设计的区别：</p>
<ul>
<li>目标是不会因为现实而改变的；</li>
<li>需求是实在做不到时可以妥协的；</li>
<li>设计是可以随便改的，不管黑猫白猫能捉到老鼠就是好猫。</li>
</ul>
<p>例：尼摩船长向多个国家的工厂定制零件，将其组装成鹦鹉螺号<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup>。</p>
<p>对这些工厂来说，它们本身的目标是赚钱——其实也不一定，但一般是赚钱。它们的需求是按尼摩船长的要求做出这些零件，从而赚取利润。它们不知道也不需要知道尼摩船长要这些零件是想干什么的，甚至它们其实都不需要知道客户是谁，来了需求就老实干呗。</p>
<p>对尼摩船长来说，他的目标可能是远离主流人类社会、追求自由、反对压迫等等。为此，他的需求是鹦鹉螺号，并且在得到鹦鹉螺号的过程中应当尽量低调。他设计的方案则是「自己设计零件；向不同国家的工厂定制这些零件；在无人岛上组装」。</p>
<br>
<p>「目标」和「需求」确实乍看很相似，但其实有本质区别：「目标」是<strong>纯粹人文</strong>的东西，而「需求」必须<strong>完全受现实制约</strong>。你可以很轻松地说「这个需求不合理，做不到，调整一下吧」；但你很难把「这个目标不合理，现实就是这样的」说出口。你最多说，「我们先从简单的做起吧」。</p>
<br>
<p>「需求」和「设计」确实很相似，但我想还是有本质区别：需求是<strong>工程直接要解决的问题</strong>，是相对根本的，是相对不可变的；设计则已经在试图解决问题，是进一步想到的，是<strong>在所有可能的方案中选了一种</strong>。</p>
<p>一种非常常见的混淆是：用不够合适的设计替代了原始需求。我们通常称这种现象为 <strong>“X-Y 问题”</strong>。</p>
<p>又例：</p>
<p>客户说：“我要一匹更快的马。”<br>
福特追问：“为什么？” → “为了跑得更快。” → “为什么？” → “为了更早到达目的地。”<br>
因此，“比马更快的汽车”也能满足客户的需求，而“一匹更快的马”某种意义上是误入歧途。</p>
<p>当然，这个追问可以不断继续下去，「更早到达目的地」其实也未必是本质的：比如说，能不能不去目的地，在原地就把事办成了？然后可能发现线上会议就可以了。</p>
<p>不过从实用性出发，不必追求绝对“终极”（必然是哲学的抽象废话），而应落在可转化为工程行动的合理深度上。</p>
<h2 id="执行-execution" class="group"><a aria-hidden="true" tabindex="-1" href="#执行-execution"><span class="icon icon-link"></span></a>执行 (execution)</h2>
<blockquote>
<p>亦称 实现 (implementation).</p>
</blockquote>
<p>「设计」 vs 「执行」，一个是意识的，一个是要与物质世界打交道。</p>
<p>不过软件工程有其特殊之处：「编程」可以说是「执行」，但它同时也是「设计」——程序设计。<br>
这就体现了程序员这个职业的特殊性了，程序员是世界上第一、也是唯一一个，能够将理念世界和物质世界直接相连的职业。</p>
<br>
<p>虽然这有时可能是最平凡的步骤，但这是最重要的步骤——化蓝图为现实。</p>
<p>行胜于言。"Ideas are cheap; execution is everything."</p>
<h2 id="评估-evaluation" class="group"><a aria-hidden="true" tabindex="-1" href="#评估-evaluation"><span class="icon icon-link"></span></a>评估/ (evaluation)</h2>
<blockquote>
<p>亦称 测试 (test) / 反馈 (feedback).</p>
</blockquote>
<p>评估「执行」的最终结果是否「足够好」地满足了「需求」（不是「目标」）。以及潜在的改进。</p>
<p>当然，虽然说评估的是“是否满足需求”，但最终极的评估仍需回看目标，因为需求可能不值得做。当需求被完美实现却未能推动目标时，说明需求分析阶段出现了偏差。</p>
<p>持续性的工程，可以根据评估的结果来改进，然后再评估，这样循环往复地迭代。以人类为主体的强化学习（</p>
<h2 id="术" class="group"><a aria-hidden="true" tabindex="-1" href="#术"><span class="icon icon-link"></span></a>术</h2>
<h3 id="背景信息-background-bg-1" class="group"><a aria-hidden="true" tabindex="-1" href="#背景信息-background-bg-1"><span class="icon icon-link"></span></a>背景信息 (background, bg)</h3>
<p>背景信息分类：</p>
<ol>
<li><strong>大背景</strong>：无意改变的客观条件。此工程需要认识并尊重这样的客观事实。</li>
<li><strong>小背景</strong>：有意改变的现状。解释了此工程的重要性/必要性，它要改造世界的这个局部。</li>
<li><strong>自身约束</strong>：我能承诺投入的资源或行为边界（如时间、预算、技术栈）。能分给此工程的资源是有限的。</li>
</ol>
<p>此外，完整的背景信息一般多到不可能列全。因此我倾向于只记录“值得记”的部分：</p>
<ol>
<li>只考虑 <strong>重要且非显然</strong> 的。其他背景信息要么无用，要么显然。</li>
<li>此外，若某条背景仅在特定步骤中有用，不妨直接嵌入到相应步骤，而非堆砌于此。保持此处的通用性与简洁性。</li>
</ol>
<p>这是必要的取舍，毕竟人脑能支持的上下文很短。</p>
<h3 id="需求" class="group"><a aria-hidden="true" tabindex="-1" href="#需求"><span class="icon icon-link"></span></a>需求</h3>
<blockquote>
<p>需求工程 (requirements engineering)</p>
</blockquote>
<ol>
<li>需求收集 (requirements collection). 收集一大堆“提案 (proposal)”，为后续分析提供充足的素材。</li>
<li>需求分析 (requirements analysis). 「需求」终究要考虑现实因素。</li>
</ol>
<br>
<p>需求分类：</p>
<ul>
<li>功能性需求 (functional requirements): shall do</li>
<li>非功能性需求 / 质量 (non-functional requirements / qualities): shall be
<ul>
<li>execution qualities: e.g. 可用性、吸引力</li>
<li>evolution qualities: e.g. 可维护性</li>
<li>constraints: e.g. 避免侵权</li>
</ul>
</li>
</ul>
<h3 id="设计" class="group"><a aria-hidden="true" tabindex="-1" href="#设计"><span class="icon icon-link"></span></a>设计</h3>
<p>这一块我好长时间都没想清楚。不过经由我自己的积累和反复迭代，以及跟AI的交流，目前我觉得是比较清楚了。</p>
<p>目前我会考虑3个分类维度：</p>
<ol>
<li>抽象层级</li>
<li>模块切分</li>
<li>复用范围</li>
</ol>
<h4 id="抽象层级" class="group"><a aria-hidden="true" tabindex="-1" href="#抽象层级"><span class="icon icon-link"></span></a>抽象层级</h4>
<p>设计经常不是一步完成的，而是要逐层细化，参考金字塔原理。非常复杂的情况，试图一步到位容易导致项目混乱和过度设计，这种坑我踩了不知道多少次，最后都只能推倒重来。日常生活中的设计也有必要从程序设计中汲取经验。</p>
<p>我的分类：</p>
<ul>
<li>概念设计 (Concept)。高屋建瓴地为需求中的关键问题提供解决思路。<strong>对终端用户来说是“知识”。</strong></li>
<li>规格设计 (Specification)。对需求进一步细化，使之具体。<strong>对终端用户来说是“信息”。</strong></li>
<li>实现方案设计 (Solution)。如何使用现有工具实现需求。<strong>对终端用户来说是不需要知道的“内部实现细节”。</strong></li>
</ul>
<p>例1：需求是获得无风险收益，“知识”是无风险收益的根本来源，“信息”是有哪些值得考虑的无风险收益渠道，“内部实现细节”是具体如何去获得。</p>
<p>例2：需求是某个某方面的软件，“知识”是该软件的设计理念，“信息”是具体有哪些功能，“内部实现细节”是技术选型和如何实现。</p>
<h4 id="模块切分" class="group"><a aria-hidden="true" tabindex="-1" href="#模块切分"><span class="icon icon-link"></span></a>模块切分</h4>
<p>在复杂项目中，经常把项目切分为多个模块，分别设计，再集成起来。整体 vs 部分。</p>
<p>具体切法依赖领域经验，就不展开了。</p>
<h4 id="复用范围" class="group"><a aria-hidden="true" tabindex="-1" href="#复用范围"><span class="icon icon-link"></span></a>复用范围</h4>
<p>我的分类：</p>
<ul>
<li>全局：原则/指导思想。例：设计原则</li>
<li>局部：模式/技巧/策略。例：设计模式</li>
<li>一次性</li>
</ul>
<hr>
<p>关联条目：</p>
<ul>
<li><a href="/post/7/%E2%80%9C%E5%88%86%E7%A7%91%E4%B9%8B%E5%AD%A6%E2%80%9D">“分科之学”</a>: “工程”的界定是其中一部分。</li>
</ul>
<section data-footnotes class="footnotes"><h2 class="sr-only group" id="footnote-label"><a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>相当程度上受「软件工程」启发。当初还没听说过马斯克的「第一性原理」，不过后来看来思路还挺一致，很高兴，我也很乐意用「第一性原理」来描述本文的内容。 <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>凡尔纳《海底两万里》 <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
        <category label="哲学"/>
        <category label="修身"/>
        <category label="软技能"/>
        <published>2025-10-19T17:24:44.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[“分科之学”]]></title>
        <id>https://www.tinystar.me/post/7</id>
        <link href="https://www.tinystar.me/post/7"/>
        <updated>2026-03-01T21:03:41.000Z</updated>
        <summary type="html"><![CDATA[初稿创建于2023年9月，不过后续陆续改动很大。我真的不知道自己给这个页面做过多少次大改了，甚至25年公开发成博客之后还有多次大改。不过，整体思路始终与最初创建时一致，没有什么颠覆，单纯是更清晰了。 Warning纯属个人观点，也不追求绝对严谨/可靠，有的部分甚至可能逻辑颇为跳跃，请勿太当回事。 事实上，整出这些东西最初只是为了指导我自己如何组织各种笔记（ 也因此，视角会略偏静态，比如把「工程」完全归入客观而忽略其中的主观能动性。 Note很多分类不是绝对的，而是相对于某个主体的。比如，法律和道德是集体意识的观点，存在一定主观性（比如，它们的客观性显然不如自然科学）；但对普通个体来说，它们是客...]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>初稿创建于2023年9月，不过后续陆续改动很大。我真的不知道自己给这个页面做过多少次大改了，甚至25年公开发成博客之后还有多次大改。不过，整体思路始终与最初创建时一致，没有什么颠覆，单纯是更清晰了。</p>
</blockquote>
<div class="markdown-alert markdown-alert-warning"><p class="markdown-alert-title"><svg class="octicon octicon-alert mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</p><p>纯属个人观点，也不追求绝对严谨/可靠，有的部分甚至可能逻辑颇为跳跃，请勿太当回事。<br>
事实上，整出这些东西最初只是为了指导我自己<strong>如何组织各种笔记</strong>（</p>
<p>也因此，视角会略偏静态，比如把「工程」完全归入客观而忽略其中的主观能动性。</p>
</div>
<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg class="octicon octicon-info mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p><p>很多分类不是绝对的，而是相对于某个主体的。比如，法律和道德是集体意识的观点，存在一定主观性（比如，它们的客观性显然不如自然科学）；但对普通个体来说，它们是客观的社会环境。</p>
</div>
<h2 id="吾谁与归" class="group"><a aria-hidden="true" tabindex="-1" href="#吾谁与归"><span class="icon icon-link"></span></a>吾谁与归</h2>
<p><strong>现象</strong> 分为 物质的 和 理念的。从 <strong>物质现象</strong> 到 <strong>理念本质</strong> 是一层抽象，而这个“理念本质”也是理念世界的一个现象，在理念世界内还可以继续抽象（数学）。</p>
<blockquote>
<p>「现象」和「本质」似乎有时在日常场景下显得有点别扭，此时好像「实例」和「理论」这组词更自然些。不过下面还是优先统一用「现象」和「本质」这组词。</p>
<p>「物质」和「理念」这组词似乎有时也是换成「感官」和「精神」更自然些？不过下面还是优先统一用「物质」和「理念」这组词。</p>
</blockquote>
<p><strong>理念世界 (Form)</strong>，与所有智能体共享。例：欧式几何、经典力学的数学模型。</p>
<p><strong>物质世界</strong>，与此世界的智能体共享。例：现实世界、Minecraft存档。</p>
<blockquote>
<p>程序员是世界上第一、也是唯一，能够将理念世界和物质世界直接相连的职业。</p>
</blockquote>
<br>
<p>在物质世界中可以诞生 <strong>智能体 (Agent)</strong>：</p>
<p><strong>智能 (Intelligence)</strong> 可调用感知和行动的接口与环境交互，根据环境的变化来调整自己的行为，以尽可能实现目标。受、想、行、识；不能够“识”的应该不太能算智能体。</p>
<blockquote>
<p>「算法」其实似乎就已经满足上述对「智能」的描述了；但通常认为「智能」还要更灵活、更可扩展一些。比如，二分查找，一般不会当成智能；蒙特卡洛树搜索，有点犹豫；大语言模型，毫无疑问是智能（有些人可能觉得并非智能。不过我觉得这类“AI虽然XX，但它从本质上就不XX”的争论很无聊，只是潜意识里人类中心论的体现，结论先行，然后给自己找论据。如果真要严格规定某个标准出来的话，我觉得在拿这个标准去拷问AI之前，最好先看看有多少人类能达到这个标准，别最后发现有相当比例的人类不具有智能[旺柴]）。</p>
</blockquote>
<p>智能是一种理念，但其目标由物质决定，又需要通过物质来提供感知和行动的接口，这个整体是为智能体。换一种说法似乎也很合适：智能是一类特殊的信息，而信息必须以物质为载体。</p>
<p>物质世界中，当前智能体之外的部分，专门称作 <strong>环境</strong>。</p>
<br>
<p>进一步，多个智能体又能聚集成为复杂系统 <strong>社会</strong>。</p>
<p>新的智能和智能体：社会往往又会涌现出 <strong>集体智能</strong>，相比 <strong>个体智能</strong> 一般会有从量变到质变的过程。但质变的结果可能是更明智，也可能是更蠢。</p>
<p>新的环境：<strong>社会环境</strong>。于是社会之外的原来的环境，专门称作 <strong>自然环境</strong>。</p>
<br>
<p>特别地，将一类特殊的智能划分出来：「我思，故我在。」因为正在思考，所以必然存在思考的主体——这就是“我”。“我”能够认识到自己的存在。将这种智能专门称作 <strong>意识</strong>。</p>
<p>于是，终于有了 <strong>主观</strong>。此外的，称作 <strong>客观</strong>。</p>
<p>另外，类似于 集体智能，自然也会有 <strong>集体意识</strong>。这部分内容通常又称作 <strong>共识主观</strong>，介于主观和客观之间。</p>
<br>
<p>挑战“自我同一性”，可以将自我意识进一步细化，分出更多类：</p>
<ul>
<li>「身体」相同</li>
<li>「记忆」相同</li>
<li>「叙事」相同（叙事自我理论）</li>
<li>「心理特征的连续性」相同（心理连续性理论）</li>
</ul>
<p>不过，从实用性出发，由于在我有生之间应该都不会被迫面对“我是谁？”这种灵魂质问，因此，可以先不做严格区分。</p>
<h2 id="宇宙万法" class="group"><a aria-hidden="true" tabindex="-1" href="#宇宙万法"><span class="icon icon-link"></span></a>宇宙万法</h2>
<p>以“我”为中心的划分方式：</p>
<ul>
<li>主观&#x26;应然&#x26;规范：<strong>人文</strong>
<ul>
<li>观点/意见/看法</li>
<li>特别地，将 艺术性 专门划分出来。</li>
</ul>
</li>
<li>客观&#x26;实然&#x26;描述：<strong>认识和改造世界</strong>
<ul>
<li>哲学 创造本质</li>
<li>数学 从本质到现象（演绎）</li>
<li>科学 从现象到本质（归纳）</li>
<li>工程 创造现象</li>
<li>技术 是抽象出的工具</li>
</ul>
<blockquote>
<p>除了哲学之外，另外几个概念相较于日常使用习惯都有所泛化。</p>
</blockquote>
</li>
</ul>
<p>下面会按这个划分方式来展开。</p>
<p>再给出另一个以“人”为中心的划分方式：</p>
<ul>
<li>跟人有关：<strong>文</strong>
<ul>
<li>人文，是最纯粹的文科</li>
<li>跟人有关的哲学</li>
<li>社会科学</li>
<li>与人有关的技术、工程</li>
</ul>
<blockquote>
<p>虽然语言中很少用，但社会科学显然也有对应的技术和工程。有「理工」自然也有「文工」</p>
</blockquote>
</li>
<li>跟物有关：<strong>理</strong>
<ul>
<li>数学，是最纯粹的理科</li>
<li>跟物有关的哲学</li>
<li>自然科学</li>
<li>与物有关的技术、工程</li>
</ul>
</li>
</ul>
<p>感觉这个划分在日常的使用频率更高，是非常实用的分类工具。</p>
<p>但是，毕竟“人”和“物”的区分在极端案例下可能存在争议，而“我”和“外界”的区分在「我思故我在」这个哲学基点的加持下就显得相当坚实，因此基本概念还是用上一个划分的比较好。</p>
<blockquote>
<p>胡言乱语成功说服自己不用修改下面的长篇大论。</p>
</blockquote>
<p>DeepSeek曰：「<br>
有了前面的铺垫，再看你提出的两套划分方式，就明白它们为什么可以并存且不矛盾了：</p>
<p>· 以“我”为中心的划分：是本体论/认识论层面的。它追问“知识从哪里来？”，答案是：要么来自“我”的主观（人文），要么来自对“外界”的客观探索（认识改造世界）。<br>
· 以“人”为中心的划分：是实用分类学层面的。它追问“知识用来干什么？”，答案是：要么用来处理“人与人/人与社会”的关系（文），要么用来处理“人与物”的关系（理）。</p>
<p>而“文工”这个概念，恰恰是在实用层面，将“认识改造世界”的方法（工程、技术）应用到了以“人”为中心的领域。它在“我”的框架里，对应的是对“社会环境”（共识主观）的客观认识和工程改造。<br>
」</p>
<h3 id="主观应然规范人文" class="group"><a aria-hidden="true" tabindex="-1" href="#主观应然规范人文"><span class="icon icon-link"></span></a>主观&#x26;应然&#x26;规范：人文</h3>
<p><strong>特征：不太在意正确性，甚至自身直接断言正确性。</strong></p>
<br>
<p>某某持有「〇〇」这种观点，自然是主观的；但「某某认为〇〇」，像这样抽离出来陈述，就是客观事实了。</p>
<p>研究这样的客观事实，则成为「社会科学」。例：</p>
<ul>
<li>是什么：社会中存在什么样的文化？</li>
<li>为什么：这样的文化是如何形成的？</li>
</ul>
<p>不断言正确性、只是作为命题的「〇〇」，也是客观的。</p>
<br>
<p>特别地，三观：</p>
<ul>
<li><strong>世界观</strong>：对整个世界的总看法和根本观点。是关于客观世界的，但要在各种都不错的可能解释中自己选1种作为标准，这个“挑选”的过程是主观的。</li>
<li><strong>人生观</strong>：对人生的看法，也就是对人类生存的目的、价值和意义的看法。<strong>目标</strong>。</li>
<li><strong>价值观</strong>：对事物价值的总看法和根本观点。<strong>立场，评价标准</strong>。</li>
</ul>
<br>
<p>特别地，考虑 <strong>艺术性</strong>。</p>
<p>艺术品本身是客观的，但其艺术性是主观的。</p>
<p>不存在先验的方式可以从所有事物中划分出“艺术品”，<strong>艺术散入各领域</strong>。</p>
<p>当然，一般认为某些领域（文学、绘画、音乐、舞蹈、电影、游戏……）是作为艺术而生；但这只是体现了人类的共性，广义的艺术岂是如此局限之物，其他领域也有艺术。</p>
<p>或可再分出2个考虑艺术的角度：</p>
<ul>
<li><strong>感官之美（即 物质）</strong>。视觉、听觉、嗅觉、味觉、触觉。</li>
<li><strong>精神之美（即 理念）</strong>。知觉。</li>
</ul>
<p>更古典的说法：「眼耳鼻舌身 意，色声香味触 法」。前半句说的是 用什么去感知，后半句说的是 被感知的对象 和 感知的结果。</p>
<p>「对艺术品的认识」也是艺术的重要组成部分；并且一般更持久、更耐回味。</p>
<h3 id="客观实然描述认识和改造世界" class="group"><a aria-hidden="true" tabindex="-1" href="#客观实然描述认识和改造世界"><span class="icon icon-link"></span></a>客观&#x26;实然&#x26;描述：认识和改造世界</h3>
<p><strong>特征：客观规律不以人的意志为转移。</strong></p>
<ul>
<li>哲学 创造本质</li>
<li>数学 从本质到现象（演绎）</li>
<li>科学 从现象到本质（归纳）</li>
<li>工程 创造现象</li>
<li>技术 是抽象出的工具</li>
</ul>
<h4 id="哲学" class="group"><a aria-hidden="true" tabindex="-1" href="#哲学"><span class="icon icon-link"></span></a>哲学</h4>
<p>哲学跟人文有着密切联系：哲学理论本身是客观的；信仰某种哲学，则是主观的。</p>
<h4 id="数学" class="group"><a aria-hidden="true" tabindex="-1" href="#数学"><span class="icon icon-link"></span></a>数学</h4>
<p>数学认识（和改造）理念世界。</p>
<blockquote>
<p>关于“改造”，Q: 数学是「发现」还是「发明」？</p>
</blockquote>
<p>数学是「<strong>从本质到现象</strong>」的演绎，只关注「<strong>若有○○假设，则有○○结论</strong>」。这套假设就圈出了一片理念世界，数学理论就在其中展开。</p>
<br>
<p>至于这套假设到底是真是假，数学不知道，也不在乎，那是哲学的事情。而<strong>只要接受了这套假设</strong>，数学理论就<strong>一定是对的</strong>——数学理论可以被「证明」。</p>
<br>
<p>但也恰恰是因为「可以证明」，数学理论其实是没有任何信息量的。只要推理能力足够强，数学理论就都是废话文学，像「3+4=4+3因为整数关于加法构成阿贝尔群」——可惜人的推理能力一般是不够强的orz。</p>
<br>
<p>所有数学理论都是对的，但有些数学理论比另一些更有用——当其他地方恰巧符合这套假设时，我们就可以利用它们直接得到一大堆结论了。</p>
<p>从这个角度出发，又可将数学理论按实用性分为：</p>
<ul>
<li>相对通用，能帮助理解更多世界</li>
<li>自成一个相对独立的理念世界</li>
</ul>
<p>当然，这种分法非常随意，而且之前认为没啥用的理论可能后来就发现了大用途（非欧几何之于广义相对论，数论之于密码学……）。比如说，很多“纯粹数学”最初并不是为了「有用」而被研究的；但如今，最纯粹的数学也面临着被应用的危险🤣。</p>
<h4 id="科学" class="group"><a aria-hidden="true" tabindex="-1" href="#科学"><span class="icon icon-link"></span></a>科学</h4>
<p>科学认识物质世界。最终可能会影响 世界观，这是科学跟人文的关联。</p>
<p>物质世界中，“我”之外的「环境」可分为「自然」和「社会」，于是科学相应地可分为「自然科学」和「社会科学」。</p>
<p>科学是「<strong>从现象到本质</strong>」的归纳，观察物质世界中的现象，归纳其中的规律。</p>
<br>
<p>因此，与数学不同，科学理论 <strong>不可证明</strong>；于是只能退而求其次，依靠 <strong>可证伪</strong> 来保障可信度。即，所有的科学理论在某种意义上都是错的——科学理论要么已经被证伪了，要么在被证伪的路上。科学并不是要寻求绝对无误的真理——“绝对无误的真理”都不过是脱离物质世界的「废话」罢了。</p>
<p>因此，<strong>科学的「方法」比「结论」更具法理性</strong>。举个极端点的例子，即使是魔法的世界，也是可以用科学的方法探索魔法的规律，搞“科学魔法”的；科学和魔法并不冲突。但最后得到的各种结论显然会与我们现在这个世界差别很大，而在事实面前还固守另一个世界的“科学结论”的话就是纯小丑了——套了层科学皮的宗教而已。</p>
<p>科学理论基本是客观的，主观性仅表现在「在各种可能的表述中选1种作为标准」；虽然这个操作空间其实也挺大了（例：不怕麻烦的话，其实也可以继续用地心说研究天体运动，毕竟参考系原则上可以任意选取）。</p>
<br>
<p>所有科学理论都是错的，但有些科学理论比另一些更有用——它们可以指导我们要怎样做才能把世界往我们想要的方向改变。</p>
<p><strong>解释世界固然好，但问题在于改变世界</strong>——即使现在只能用来解释世界，那至少也要有在未来能用来改变世界的指望！</p>
<p>因此不影响指导实践的所谓“本质”就不那么重要了——不过是玩弄概念罢了，可以是数学、是哲学、是艺术，甚至可以三个同时都是，唯独不是科学。</p>
<blockquote>
<p>「工具主义将评价科学理论的基础从“到底该理论提出的现象是否真实存在？”移开，而改为分析该理论提出的结果和评价是否能够跟观察得到的现象互相符合。」——<a href="https://zh.wikipedia.org/wiki/%E5%B7%A5%E5%85%B7%E4%B8%BB%E7%BE%A9" rel="nofollow noopener noreferrer" target="_blank">工具主义 - 维基百科</a></p>
</blockquote>
<br>
<p>也因为要追求实际了，所以我们最好做出一些合理的、必要的假设，以免怀疑一切而一无所得：</p>
<ol>
<li>我们观察的事物都是有特征的，there are specific causes for events observed in the natural world,</li>
<li>这些特征是可以识别的，that the causes can be identified,</li>
<li>自然界当中发生的事件可以通过被普遍接受的方式描述出来，that there are general rules or patterns that can be used to describe what happens in nature,</li>
<li>可以重复的事件可能含有共同的特征，that an event that occurs repeatedly probably has the same cause,</li>
<li>一个人可以感知的，其他人也可以感知，that what one person perceives can be perceived by others, and</li>
<li>基本自然法则不因时间空间改变。that the same fundamental rules of nature apply regardless of where and when they occur.</li>
</ol>
<p>这样，再允许「误差项」的存在，我们甚至有望得到“绝对正确”的科学理论。<a href="https://www.changhai.org/articles/science/misc/AbsoluteTruth.php" rel="nofollow noopener noreferrer" target="_blank">关于 “绝对正确” 的科学理论 - 卢昌海个人主页</a>. 比如严格来说经典力学已经被证伪了，但在宏观低速场景下仍然可以当它是对的，因为误差在允许范围内。</p>
<br>
<p>其实我觉得科学跟机器学习简直是一回事（虽然这样说可能有点“父亲像儿子”了），机器学习甚至形式上更加严谨（毕竟要表述完全严谨才能让计算机正确执行），只是<strong>假设空间跟人类的科学研究有相当大的不同</strong>（科学理论有很强的可解释性要求，机器学习容许黑箱模型、结构简单而用参数量来弥补质的不足）。</p>
<p>二者的方法论是基本共通的。二者的目标都是「预测」。科学研究的过程严格阐述出来的话，其实就是机器学习中训练模型的「<strong>训练集、验证集、测试集</strong>」——第一课就会学。面对多个备选模型，科学有一套较为成熟的方法论来选出其中大概更有用的，而同样的，机器学习第一课就会学「<strong>欠拟合&#x26;过拟合 underfit &#x26; overfit</strong>」、「<strong>模型选择 Model Selection</strong>」。测试集泄露会导致对模型水平的评估产生争议，只能马后炮解释既有现象而无法预言新现象的科学理论同样也是 "not even wrong".</p>
<h4 id="技术" class="group"><a aria-hidden="true" tabindex="-1" href="#技术"><span class="icon icon-link"></span></a>技术</h4>
<p>技术是<strong>发明出来的工具，解决“怎么做”的问题</strong>，为工程服务。即 方法论。</p>
<p>可将技术视为一种「<strong>抽象</strong>」——分「<strong>对外接口</strong>」与「<strong>内部实现</strong>」。学习技术通常只要掌握「接口」就可以了，「实现」是工程的事；毕竟又不一定会参与技术的进一步改进。人话：会用就行。</p>
<br>
<p>可惜经常存在「<strong>抽象泄露 leaky abstraction</strong>」的现象，此时想“用好”就得深究原理。</p>
<blockquote>
<p>例：编程时为了得到更优的性能，甚至可能要了解 CPU/GPU的cache层级，这中间隔了许多层抽象。</p>
</blockquote>
<p>不过，即使存在抽象泄露，技术也依旧是有用的——学习原理的时间可能是没能省掉，但至少不用自己重新发明一遍了。</p>
<br>
<p>总结，抽象的意义：</p>
<ol>
<li><strong>隔离复杂性，以便组合。</strong></li>
<li><strong>复用。</strong></li>
</ol>
<br>
<p>注意不要混淆了「实现 implementation」和「抽象 abstraction」。A可以用B来实现，不代表A就是一种B。例：自然数可以用集合构造，不代表自然数就是集合。相反地，在用集合构造出了自然数之后，<strong>反而要彻底抽象掉这层实现</strong>，不要搞出「1∈2」这种笑话命题。</p>
<br>
虽然技术跟哲学/数学/科学/工程一样可以被当做“无用”的“纯粹艺术”，但技术更容易出现「出发点是实用的，但千转百绕、背景变迁后实际已经不实用了」的情况，从而似乎更容易陷入某种尴尬的自我矛盾的境地：对其实没什么用的技术充满热情，不是因为明知无用但就是喜欢，而是因为没意识到它对自己无用。
<h4 id="软" class="group"><a aria-hidden="true" tabindex="-1" href="#软"><span class="icon icon-link"></span></a>软○○</h4>
<p>典例：“软科学”。<a href="https://en.wikipedia.org/wiki/Hard_and_soft_science" rel="nofollow noopener noreferrer" target="_blank">Hard and soft science - Wikipedia</a></p>
<p>由于「研究对象」和「人类当下能力」的限制，研究风格跟通常意义上的“硬○○”有相当大的区别。</p>
<p>但论目标（认识和改造世界），与通常意义的是一致的。</p>
<p>虽然拆开黑箱建立准确的模型再好不过，但一时半会做不到时，经验主义该用也得用——虽然有这样那样的弊病，但<strong>总比没有好，要肯定其价值</strong>。当然，<strong>应当向着体系化的、更科学的方向努力</strong>。</p>
<h4 id="工程" class="group"><a aria-hidden="true" tabindex="-1" href="#工程"><span class="icon icon-link"></span></a>工程</h4>
<p>改造世界。组合使用各种技术，实现需求、达成目标。</p>
<p><a href="/post/10/%E5%B7%A5%E7%A8%8B%EF%BC%9A%E8%83%8C%E6%99%AF%E3%80%81%E7%9B%AE%E6%A0%87%E3%80%81%E9%9C%80%E6%B1%82%E3%80%81%E8%AE%BE%E8%AE%A1%E3%80%81%E6%89%A7%E8%A1%8C%E3%80%81%E8%AF%84%E4%BC%B0">工程：背景、目标、需求、设计、执行、评估</a>.</p>]]></content>
        <category label="哲学"/>
        <category label="修身"/>
        <category label="软技能"/>
        <published>2025-09-14T10:51:04.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[LeetCode Hot100（含扩展）]]></title>
        <id>https://www.tinystar.me/post/11</id>
        <link href="https://www.tinystar.me/post/11"/>
        <updated>2025-11-20T11:56:13.000Z</updated>
        <summary type="html"><![CDATA[WarningTODO: 目前只是大纲，具体题目的思路和代码未全部完成，持续更新中…… 本篇是大纲性质。 LeetCode Hot100 的重要性 为了应对面试中的算法题，刷 LeetCode Hot100 的性价比极高——经典题目往往是高频考题。 其他方面足够好的话，算法题其实就是走个流程，不会太难的，掌握 Hot100 基本够用了。 不过本系列的题单还是在 LeetCode Hot100 的基础上做了一些调整。 进一步节约突击用时 直接看题解 不做题，直接看题解，理解思路即可。 很多题见过相应思路后就一点都不难，但那个思路第一次做时很难独立想到——毕竟那些算法是多少前辈们的思维结晶啊！因此...]]></summary>
        <content type="html"><![CDATA[<div class="markdown-alert markdown-alert-warning"><p class="markdown-alert-title"><svg class="octicon octicon-alert mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</p><p>TODO: 目前只是大纲，具体题目的思路和代码未全部完成，持续更新中……</p>
</div>
<p>本篇是大纲性质。</p>
<h2 id="leetcode-hot100-的重要性" class="group"><a aria-hidden="true" tabindex="-1" href="#leetcode-hot100-的重要性"><span class="icon icon-link"></span></a>LeetCode Hot100 的重要性</h2>
<p>为了应对面试中的算法题，刷 LeetCode Hot100 的性价比极高——经典题目往往是高频考题。</p>
<p>其他方面足够好的话，算法题其实就是走个流程，不会太难的，掌握 Hot100 基本够用了。</p>
<p>不过本系列的题单还是在 LeetCode Hot100 的基础上做了一些调整。</p>
<h2 id="进一步节约突击用时" class="group"><a aria-hidden="true" tabindex="-1" href="#进一步节约突击用时"><span class="icon icon-link"></span></a>进一步节约突击用时</h2>
<h3 id="直接看题解" class="group"><a aria-hidden="true" tabindex="-1" href="#直接看题解"><span class="icon icon-link"></span></a>直接看题解</h3>
<p>不做题，直接看题解，理解思路即可。</p>
<p>很多题见过相应思路后就一点都不难，但那个思路第一次做时很难独立想到——毕竟那些算法是多少前辈们的思维结晶啊！因此在面试中才在现场从零开始想是很不划算的，哪怕草草看一下大致思路也会很有帮助，细节可以现场发挥补全。</p>
<p>技巧再难想，我看过了就是我的😁。</p>
<h3 id="进一步筛选题目" class="group"><a aria-hidden="true" tabindex="-1" href="#进一步筛选题目"><span class="icon icon-link"></span></a>进一步筛选题目</h3>
<p>太简单的题目不刷也能现场想出，（算法竞赛级别的难题也不会被拿来刁难人，）因此需要重点突击那些 <strong>有一定难度</strong> 的。这些题目有一定难度，但毕竟被列入了 LeetCode Hot100，很适合面试官用来“不那么明显”地放水——此时要是候选人完全写不出来就很尴尬了……</p>
<p>LeetCode 官方的「简单」、「中等」、「困难」的评级过于粗糙，而且一些题目的评级其实并不准确。更推荐使用第三方脚本 <a href="https://github.com/zhang-wangz/LeetCodeRating" rel="nofollow noopener noreferrer" target="_blank">LeetCodeRating</a> 里的 评级 和 难度分 来评估题目难度，我个人觉得参考价值更高一些。</p>
<p>Hot100 中其实还是有冷热之分。热中热，不可不会（尤其是前2题，LRU缓存 和 合并K个升序链表）：</p>
<ul>
<li><a href="https://leetcode.cn/problems/lru-cache/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">LRU 缓存</a>（评级: 7）. <a href="/post/14/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8B%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A76~7#lru%E7%BC%93%E5%AD%98%E8%AF%84%E7%BA%A77">我的解答</a></li>
<li><a href="https://leetcode.cn/problems/merge-k-sorted-lists/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">合并 K 个升序链表</a>（评级: 6）. <a href="/post/14/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8B%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A76~7#%E5%90%88%E5%B9%B6-k-%E4%B8%AA%E5%8D%87%E5%BA%8F%E9%93%BE%E8%A1%A8%E8%AF%84%E7%BA%A76">我的解答</a></li>
<li><a href="https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">无重复字符的最长子串</a>（评级: 5）</li>
<li><a href="https://leetcode.cn/problems/longest-increasing-subsequence/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">最长递增子序列</a>（评级: 4）</li>
<li><a href="https://leetcode.cn/problems/binary-tree-right-side-view/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">二叉树的右视图</a>（评级: 4）</li>
<li><a href="https://leetcode.cn/problems/maximum-subarray/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">最大子数组和</a>（评级: 3）</li>
<li><a href="https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">合并区间</a>（评级: 5）</li>
<li><a href="https://leetcode.cn/problems/longest-consecutive-sequence/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">最长连续序列</a>（评级: 6）. <a href="/post/12/leetcode-hot100-%E5%93%88%E5%B8%8C%E7%AF%87#%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97%E8%AF%84%E7%BA%A76">我的解答</a></li>
<li><a href="https://leetcode.cn/problems/subarray-sum-equals-k/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">和为 K 的子数组</a>（评级: 5）</li>
<li><a href="https://leetcode.cn/problems/partition-equal-subset-sum/description/" rel="nofollow noopener noreferrer" target="_blank">分割等和子集</a>（评级: 5）</li>
<li><a href="https://leetcode.cn/problems/kth-largest-element-in-an-array/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">数组中的第K个最大元素</a>（评级: 6）</li>
<li><a href="https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">从前序与中序遍历序列构造二叉树</a>（评级: 5）</li>
<li><a href="https://leetcode.cn/problems/reverse-nodes-in-k-group/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">K 个一组翻转链表</a>（评级: 4）. <a href="/post/13/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8A%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A72~4#k-%E4%B8%AA%E4%B8%80%E7%BB%84%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8%E8%AF%84%E7%BA%A74">我的解答</a></li>
<li><a href="https://leetcode.cn/problems/find-median-from-data-stream/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">数据流的中位数</a>（评级: 6）</li>
<li><a href="https://leetcode.cn/problems/number-of-islands/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">岛屿数量</a>（评级: 4）</li>
<li><a href="https://leetcode.cn/problems/trapping-rain-water/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">接雨水</a>（评级: 6）</li>
<li><a href="https://leetcode.cn/problems/longest-palindromic-substring/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">最长回文子串</a>（评级: 5）</li>
<li><a href="https://leetcode.cn/problems/median-of-two-sorted-arrays/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">寻找两个正序数组的中位数</a>（评级: 8）</li>
</ul>
<h2 id="全部思路和代码" class="group"><a aria-hidden="true" tabindex="-1" href="#全部思路和代码"><span class="icon icon-link"></span></a>全部思路和代码</h2>
<p>按 <strong>我的风格</strong> 列出题目、思路和代码，其中思路和代码是默认折叠的。代码会力求写得 <strong>「显然正确」</strong>。</p>
<p>有对题单做出一些改动，像是 合并题量较少的相似分组、拆分题目过多风格有异的大分组、添加新分组；<strong>添加少量高频题</strong>、替换题目、删除意义不大的题目。</p>
<ul>
<li><a href="/post/12/leetcode-hot100-%E5%93%88%E5%B8%8C%E7%AF%87">哈希</a></li>
<li>双指针</li>
<li>滑动窗口和子串</li>
<li>普通数组</li>
<li>矩阵</li>
<li>链表
<ul>
<li><a href="/post/13/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8A%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A72~4">上篇：评级2~4</a></li>
<li><a href="/post/14/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8B%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A76~7">下篇：评级6~7</a></li>
</ul>
</li>
<li>二叉树</li>
<li>图论</li>
<li>回溯</li>
<li>二分查找</li>
<li>栈</li>
<li>堆</li>
<li>贪心算法</li>
<li>动态规划</li>
<li>多维动态规划</li>
<li>技巧</li>
<li>数学</li>
</ul>]]></content>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-11-12T14:36:20.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[LeetCode Hot100 链表篇（下）：评级6~7]]></title>
        <id>https://www.tinystar.me/post/14</id>
        <link href="https://www.tinystar.me/post/14"/>
        <updated>2025-11-16T12:13:36.000Z</updated>
        <summary type="html"><![CDATA[组合父条目： LeetCode Hot100 随机链表的复制（评级6） 在常规单链表的基础上，有额外的 random 指针域，随机指向链表中的任意节点（包括空节点）。 关键点：新链表中的指针指向的是新链表中的节点，而不是原链表中的节点。 思路 最简明的想法是用一个 map 来维护从 原链表节点 到 新链表节点 的映射关系。 还有不用 map 的做法，可以做到 O(1)O(1)O(1) 额外空间： 把新节点直接插入到旧节点后面，形成交错链表。 遍历交错链表，为新节点设置 random 指针域。 从这个交错链表中将新节点分离出来。 我觉得这个算法其实不很实用，因为新链表本身都要 Θ(n)\Thet...]]></summary>
        <content type="html"><![CDATA[<p>组合父条目：</p>
<ul>
<li><a href="/post/11/leetcode-hot100">LeetCode Hot100</a></li>
</ul>
<hr>
<h2 id="随机链表的复制评级6" class="group"><a aria-hidden="true" tabindex="-1" href="#随机链表的复制评级6"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/copy-list-with-random-pointer/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">随机链表的复制</a>（评级6）</h2>
<p>在常规单链表的基础上，有额外的 <code>random</code> 指针域，随机指向链表中的任意节点（包括空节点）。</p>
<p>关键点：新链表中的指针指向的是新链表中的节点，而不是原链表中的节点。</p>
<details>
<summary>
思路
</summary>
<p>最简明的想法是用一个 map 来维护从 原链表节点 到 新链表节点 的映射关系。</p>
<hr>
<p>还有不用 map 的做法，可以做到 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord">1</span><span class="mclose">)</span></span></span></span> 额外空间：</p>
<ol>
<li>把新节点直接插入到旧节点后面，形成交错链表。</li>
<li>遍历交错链表，为新节点设置 <code>random</code> 指针域。</li>
<li>从这个交错链表中将新节点分离出来。</li>
</ol>
<p>我觉得这个算法其实不很实用，因为新链表本身都要 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">Θ</mi><mo stretchy="false">(</mo><mi>n</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\Theta(n)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">Θ</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mclose">)</span></span></span></span> 空间了，所谓 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord">1</span><span class="mclose">)</span></span></span></span> 的额外空间复杂度某种意义上也只是虚假的<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord">1</span><span class="mclose">)</span></span></span></span>……</p>
<p>不过“交错链表”的技巧确实很巧妙。</p>
</details>
<details>
<summary>
代码
</summary>
<p>使用 map 维护从 原链表节点 到 新链表节点 的映射关系：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">copyRandomList</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">  if</span><span style="color:#ADBAC7"> (head </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">) </span><span style="color:#F47067">return</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 用一个 map 来维护从 原链表节点 到 新链表节点 的映射关系</span></span>
<span data-line=""><span style="color:#ADBAC7">  unordered_map</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">Node </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">, Node </span><span style="color:#F47067">*></span><span style="color:#ADBAC7"> node_map;</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head; cur; cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    node_map[cur] </span><span style="color:#F47067">=</span><span style="color:#F47067"> new</span><span style="color:#DCBDFB"> Node</span><span style="color:#ADBAC7">(cur->val);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 第二次遍历，设置 next 和 random 指针</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head; cur; cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    node_map[cur]->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> node_map[cur->next];</span></span>
<span data-line=""><span style="color:#ADBAC7">    node_map[cur]->random </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> node_map[cur->random];</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> node_map[head];</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="Node *copyRandomList(Node *head) {
  if (head == nullptr) return nullptr;

  // 用一个 map 来维护从 原链表节点 到 新链表节点 的映射关系
  unordered_map<Node *, Node *> node_map;
  for (auto cur = head; cur; cur = cur->next) {
    node_map[cur] = new Node(cur->val);
  }

  // 第二次遍历，设置 next 和 random 指针
  for (auto cur = head; cur; cur = cur->next) {
    node_map[cur]->next = node_map[cur->next];
    node_map[cur]->random = node_map[cur->random];
  }

  return node_map[head];
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>不使用 map，而是先构建交错链表，再分离出新链表：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">copyRandomList</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">  if</span><span style="color:#ADBAC7"> (head </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">) </span><span style="color:#F47067">return</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 把新节点直接插入到旧节点后面，形成交错链表</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head; cur; cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    cur->next </span><span style="color:#F47067">=</span><span style="color:#F47067"> new</span><span style="color:#DCBDFB"> Node</span><span style="color:#ADBAC7">(cur->val, cur->next, </span><span style="color:#6CB6FF">nullptr</span><span style="color:#ADBAC7">);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 遍历交错链表，为新节点设置 `random` 指针域</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head; cur; cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next->next) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> new_node </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    new_node->random </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->random </span><span style="color:#F47067">?</span><span style="color:#ADBAC7"> cur->random->next </span><span style="color:#F47067">:</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 从这个交错链表中将新节点分离出来</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> new_head </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head; cur; cur </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> new_node </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cur->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    cur->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> new_node->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    new_node->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> new_node->next </span><span style="color:#F47067">?</span><span style="color:#ADBAC7"> new_node->next->next </span><span style="color:#F47067">:</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> new_head;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="Node *copyRandomList(Node *head) {
  if (head == nullptr) return nullptr;

  // 把新节点直接插入到旧节点后面，形成交错链表
  for (auto cur = head; cur; cur = cur->next->next) {
    cur->next = new Node(cur->val, cur->next, nullptr);
  }

  // 遍历交错链表，为新节点设置 &#x60;random&#x60; 指针域
  for (auto cur = head; cur; cur = cur->next->next) {
    auto new_node = cur->next;
    new_node->random = cur->random ? cur->random->next : nullptr;
  }

  // 从这个交错链表中将新节点分离出来
  auto new_head = head->next;
  for (auto cur = head; cur; cur = cur->next) {
    auto new_node = cur->next;
    cur->next = new_node->next;
    new_node->next = new_node->next ? new_node->next->next : nullptr;
  }
  return new_head;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="排序链表评级6" class="group"><a aria-hidden="true" tabindex="-1" href="#排序链表评级6"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/sort-list/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">排序链表</a>（评级6）</h2>
<details>
<summary>
思路
</summary>
<p>归并排序对「查找」的要求低，是对链表排序的最佳方案。</p>
<p>链表的归并排序 需要用到 <a href="/post/13/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8A%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A72~4#%E9%93%BE%E8%A1%A8%E7%9A%84%E4%B8%AD%E9%97%B4%E7%BB%93%E7%82%B9%E8%AF%84%E7%BA%A73">链表的中间结点</a> 和 <a href="/post/13/leetcode-hot100-%E9%93%BE%E8%A1%A8%E7%AF%87%EF%BC%88%E4%B8%8A%EF%BC%89%EF%BC%9A%E8%AF%84%E7%BA%A72~4#%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8%E8%AF%84%E7%BA%A73">合并两个有序链表</a>。</p>
</details>
<details>
<summary>
代码
</summary>
<p>我写了检查 <code>left_tail->val &#x3C;= right_head->val</code> 的优化，因此为了克服单链表不能反向迭代的困难，额外写了一些获取尾节点的代码。不写这个优化的话，可以再简化一些。</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">class</span><span style="color:#F69D50"> Solution</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">public:</span></span>
<span data-line=""><span style="color:#F69D50">  ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">sortList</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">    // 归并排序</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> [new_head, new_tail] </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> sortListWithTail</span><span style="color:#ADBAC7">(head);</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> new_head;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">private:</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> HeadTail</span><span style="color:#F47067"> =</span><span style="color:#F69D50"> pair</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#ADBAC7">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 返回 排序后链表的 头节点 和 尾节点</span></span>
<span data-line=""><span style="color:#F69D50">  HeadTail</span><span style="color:#DCBDFB"> sortListWithTail</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (head </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#F47067"> ||</span><span style="color:#ADBAC7"> head->next </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">      return</span><span style="color:#ADBAC7"> {head, head};</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 切分链表</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> mid_prev </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> prev_of_middleNode</span><span style="color:#ADBAC7">(head);</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> mid </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> mid_prev->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    mid_prev->next </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 递归排序左右子链表</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> [left_head, left_tail] </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> sortListWithTail</span><span style="color:#ADBAC7">(head);</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> [right_head, right_tail] </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> sortListWithTail</span><span style="color:#ADBAC7">(mid);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 剪枝优化</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (left_tail->val </span><span style="color:#F47067">&#x3C;=</span><span style="color:#ADBAC7"> right_head->val) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      left_tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> right_head;</span></span>
<span data-line=""><span style="color:#F47067">      return</span><span style="color:#ADBAC7"> {left_head, right_tail};</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#DCBDFB"> mergeTwoListsWithTail</span><span style="color:#ADBAC7">(left_head, right_head, left_tail, right_tail);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">  ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">prev_of_middleNode</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode </span><span style="color:#DCBDFB">pre</span><span style="color:#ADBAC7">(</span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7">, head);</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> slow </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">pre;</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#ADBAC7"> (fast </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> fast->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> slow->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> fast->next->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> slow;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">  HeadTail</span><span style="color:#DCBDFB"> mergeTwoListsWithTail</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">list1</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">list2</span><span style="color:#ADBAC7">,</span></span>
<span data-line=""><span style="color:#F69D50">                                 ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">tail1</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">tail2</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode dummy;</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">tail </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (; list1 </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> list2; tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> tail->next) {</span></span>
<span data-line=""><span style="color:#F47067">      if</span><span style="color:#ADBAC7"> (list1->val </span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7"> list2->val) {</span></span>
<span data-line=""><span style="color:#ADBAC7">        tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list1;</span></span>
<span data-line=""><span style="color:#ADBAC7">        list1 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list1->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      } </span><span style="color:#F47067">else</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#ADBAC7">        tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list2;</span></span>
<span data-line=""><span style="color:#ADBAC7">        list2 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list2->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      }</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 拼接剩余部分</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (list1) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list1;</span></span>
<span data-line=""><span style="color:#ADBAC7">      tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> tail1;</span></span>
<span data-line=""><span style="color:#ADBAC7">    } </span><span style="color:#F47067">else</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#ADBAC7">      tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list2;</span></span>
<span data-line=""><span style="color:#ADBAC7">      tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> tail2;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> {dummy.next, tail};</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#ADBAC7">};</span></span><button type="button" title="Copy code" aria-label="Copy code" data="class Solution {
public:
  ListNode *sortList(ListNode *head) {
    // 归并排序
    auto [new_head, new_tail] = sortListWithTail(head);
    return new_head;
  }

private:
  using HeadTail = pair<ListNode *, ListNode *>;

  // 返回 排序后链表的 头节点 和 尾节点
  HeadTail sortListWithTail(ListNode *head) {
    if (head == nullptr || head->next == nullptr) {
      return {head, head};
    }

    // 切分链表
    auto mid_prev = prev_of_middleNode(head);
    auto mid = mid_prev->next;
    mid_prev->next = nullptr;

    // 递归排序左右子链表
    auto [left_head, left_tail] = sortListWithTail(head);
    auto [right_head, right_tail] = sortListWithTail(mid);

    // 剪枝优化
    if (left_tail->val <= right_head->val) {
      left_tail->next = right_head;
      return {left_head, right_tail};
    }

    return mergeTwoListsWithTail(left_head, right_head, left_tail, right_tail);
  }

  ListNode *prev_of_middleNode(ListNode *head) {
    ListNode pre(0, head);
    auto slow = &#x26;pre;
    auto fast = head;

    while (fast &#x26;&#x26; fast->next) {
      slow = slow->next;
      fast = fast->next->next;
    }

    return slow;
  }

  HeadTail mergeTwoListsWithTail(ListNode *list1, ListNode *list2,
                                 ListNode *tail1, ListNode *tail2) {
    ListNode dummy;
    ListNode *tail = &#x26;dummy;

    for (; list1 &#x26;&#x26; list2; tail = tail->next) {
      if (list1->val < list2->val) {
        tail->next = list1;
        list1 = list1->next;
      } else {
        tail->next = list2;
        list2 = list2->next;
      }
    }

    // 拼接剩余部分
    if (list1) {
      tail->next = list1;
      tail = tail1;
    } else {
      tail->next = list2;
      tail = tail2;
    }
    return {dummy.next, tail};
  }
};" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="合并-k-个升序链表评级6" class="group"><a aria-hidden="true" tabindex="-1" href="#合并-k-个升序链表评级6"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/merge-k-sorted-lists/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">合并 K 个升序链表</a>（评级6）</h2>
<p>高频考题！</p>
<details>
<summary>
思路
</summary>
<p><code>merge</code>，但需要快速从多个链表的当前节点中选出最小节点，用小顶堆。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">mergeKLists</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#ADBAC7">> </span><span style="color:#F47067">&#x26;</span><span style="color:#F69D50">lists</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 需快速从多个链表的当前节点中选出最小节点，用小顶堆</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> cmp </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> [](</span><span style="color:#F47067">const</span><span style="color:#F69D50"> ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">a</span><span style="color:#ADBAC7">, </span><span style="color:#F47067">const</span><span style="color:#F69D50"> ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">b</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> a->val </span><span style="color:#F47067">></span><span style="color:#ADBAC7"> b->val;</span></span>
<span data-line=""><span style="color:#ADBAC7">  };</span></span>
<span data-line=""><span style="color:#ADBAC7">  priority_queue</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">ListNode </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">, vector</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">ListNode </span><span style="color:#F47067">*></span><span style="color:#ADBAC7">, </span><span style="color:#F47067">decltype</span><span style="color:#ADBAC7">(cmp)</span><span style="color:#F47067">></span><span style="color:#DCBDFB"> pq</span><span style="color:#ADBAC7">(cmp);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> head : lists) {</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (head) pq.</span><span style="color:#DCBDFB">push</span><span style="color:#ADBAC7">(head);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#ADBAC7">  ListNode dummy;</span></span>
<span data-line=""><span style="color:#768390">  // merge</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> tail </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy; </span><span style="color:#F47067">!</span><span style="color:#ADBAC7">pq.</span><span style="color:#DCBDFB">empty</span><span style="color:#ADBAC7">(); ) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> node </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> pq.</span><span style="color:#DCBDFB">top</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""><span style="color:#ADBAC7">    pq.</span><span style="color:#DCBDFB">pop</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""><span style="color:#ADBAC7">    tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> node;</span></span>
<span data-line=""><span style="color:#768390">    // update</span></span>
<span data-line=""><span style="color:#ADBAC7">    tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> tail->next;</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (node->next) pq.</span><span style="color:#DCBDFB">push</span><span style="color:#ADBAC7">(node->next);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *mergeKLists(vector<ListNode *> &#x26;lists) {
  // 需快速从多个链表的当前节点中选出最小节点，用小顶堆
  auto cmp = [](const ListNode *a, const ListNode *b) {
    return a->val > b->val;
  };
  priority_queue<ListNode *, vector<ListNode *>, decltype(cmp)> pq(cmp);

  for (auto head : lists) {
    if (head) pq.push(head);
  }

  ListNode dummy;
  // merge
  for (auto tail = &#x26;dummy; !pq.empty(); ) {
    auto node = pq.top();
    pq.pop();
    tail->next = node;
    // update
    tail = tail->next;
    if (node->next) pq.push(node->next);
  }

  return dummy.next;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="lru缓存评级7" class="group"><a aria-hidden="true" tabindex="-1" href="#lru缓存评级7"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/lru-cache/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">LRU缓存</a>（评级7）</h2>
<p>高频考题！</p>
<p>要求 <code>get()</code>, <code>put()</code> 都是 <code>O(1)</code> 时间复杂度。</p>
<details>
<summary>
思路
</summary>
<p>访问某个节点后，需将其移动到序列的头部。为了高效实现这点，我们需要用链表来存储 cache line.</p>
<p>又因为 <code>get()</code> 得是 <code>O(1)</code> 的，所以我们要用HashMap来做索引，HashMap的Value就是链表中对应缓存行的迭代器。</p>
<p>这一套下来其实就是 "LinkedHashMap".</p>
</details>
<details>
<summary>
代码
</summary>
<p>使用 STL 中现成的链表：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">class</span><span style="color:#F69D50"> LRUCache</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">public:</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> Key</span><span style="color:#F47067"> =</span><span style="color:#F47067"> int</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> Value</span><span style="color:#F47067"> =</span><span style="color:#F47067"> int</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#DCBDFB">  LRUCache</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">int</span><span style="color:#F69D50"> capacity</span><span style="color:#ADBAC7">) : </span><span style="color:#DCBDFB">capacity_</span><span style="color:#ADBAC7">(capacity) {}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">  Value</span><span style="color:#DCBDFB"> get</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Key</span><span style="color:#F69D50"> key</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">find</span><span style="color:#ADBAC7">(key);</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (it </span><span style="color:#F47067">==</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">end</span><span style="color:#ADBAC7">()) </span><span style="color:#F47067">return</span><span style="color:#F47067"> -</span><span style="color:#6CB6FF">1</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> cache_it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> it->second;</span></span>
<span data-line=""><span style="color:#DCBDFB">    move_to_front</span><span style="color:#ADBAC7">(cache_it);</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> cache_it->second;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  void</span><span style="color:#DCBDFB"> put</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Key</span><span style="color:#F69D50"> key</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">Value</span><span style="color:#F69D50"> value</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">find</span><span style="color:#ADBAC7">(key);</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (it </span><span style="color:#F47067">!=</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">end</span><span style="color:#ADBAC7">()) {</span></span>
<span data-line=""><span style="color:#768390">      // key 已存在，更新</span></span>
<span data-line=""><span style="color:#F47067">      auto</span><span style="color:#ADBAC7"> cache_it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> it->second;</span></span>
<span data-line=""><span style="color:#ADBAC7">      cache_it->second </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> value;</span></span>
<span data-line=""><span style="color:#DCBDFB">      move_to_front</span><span style="color:#ADBAC7">(cache_it);</span></span>
<span data-line=""><span style="color:#F47067">      return</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // key 不存在，插入新节点</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (cache_.</span><span style="color:#DCBDFB">size</span><span style="color:#ADBAC7">() </span><span style="color:#F47067">==</span><span style="color:#ADBAC7"> capacity_) {</span></span>
<span data-line=""><span style="color:#768390">      // 容量已满，驱逐尾部节点</span></span>
<span data-line=""><span style="color:#ADBAC7">      key_to_iter_.</span><span style="color:#DCBDFB">erase</span><span style="color:#ADBAC7">(cache_.</span><span style="color:#DCBDFB">back</span><span style="color:#ADBAC7">().first);</span></span>
<span data-line=""><span style="color:#ADBAC7">      cache_.</span><span style="color:#DCBDFB">pop_back</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#ADBAC7">    cache_.</span><span style="color:#DCBDFB">emplace_front</span><span style="color:#ADBAC7">(key, value);</span></span>
<span data-line=""><span style="color:#ADBAC7">    key_to_iter_[key] </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cache_.</span><span style="color:#DCBDFB">begin</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">private:</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> KV</span><span style="color:#F47067"> =</span><span style="color:#F69D50"> pair</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">Key</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">Value</span><span style="color:#ADBAC7">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  int</span><span style="color:#ADBAC7"> capacity_;</span></span>
<span data-line=""><span style="color:#768390">  // 访问某个节点后，需将其移动到序列的头部</span></span>
<span data-line=""><span style="color:#ADBAC7">  list</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">KV</span><span style="color:#F47067">></span><span style="color:#ADBAC7"> cache_;</span></span>
<span data-line=""><span style="color:#768390">  // 访问某个节点时，需能快速定位到该节点的位置</span></span>
<span data-line=""><span style="color:#ADBAC7">  unordered_map</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">Key, </span><span style="color:#F47067">decltype</span><span style="color:#ADBAC7">(cache_)::iterator</span><span style="color:#F47067">></span><span style="color:#ADBAC7"> key_to_iter_;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  void</span><span style="color:#DCBDFB"> move_to_front</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">decltype</span><span style="color:#ADBAC7">(cache_)::</span><span style="color:#F69D50">iterator</span><span style="color:#F69D50"> it</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    cache_.</span><span style="color:#DCBDFB">splice</span><span style="color:#ADBAC7">(cache_.</span><span style="color:#DCBDFB">begin</span><span style="color:#ADBAC7">(), cache_, it);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#ADBAC7">};</span></span><button type="button" title="Copy code" aria-label="Copy code" data="class LRUCache {
public:
  using Key = int;
  using Value = int;

  LRUCache(int capacity) : capacity_(capacity) {}

  Value get(Key key) {
    auto it = key_to_iter_.find(key);
    if (it == key_to_iter_.end()) return -1;
    auto cache_it = it->second;
    move_to_front(cache_it);
    return cache_it->second;
  }

  void put(Key key, Value value) {
    auto it = key_to_iter_.find(key);
    if (it != key_to_iter_.end()) {
      // key 已存在，更新
      auto cache_it = it->second;
      cache_it->second = value;
      move_to_front(cache_it);
      return;
    }

    // key 不存在，插入新节点
    if (cache_.size() == capacity_) {
      // 容量已满，驱逐尾部节点
      key_to_iter_.erase(cache_.back().first);
      cache_.pop_back();
    }
    cache_.emplace_front(key, value);
    key_to_iter_[key] = cache_.begin();
  }

private:
  using KV = pair<Key, Value>;

  int capacity_;
  // 访问某个节点后，需将其移动到序列的头部
  list<KV> cache_;
  // 访问某个节点时，需能快速定位到该节点的位置
  unordered_map<Key, decltype(cache_)::iterator> key_to_iter_;

  void move_to_front(decltype(cache_)::iterator it) {
    cache_.splice(cache_.begin(), cache_, it);
  }
};" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>手写链表和所需函数：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">class</span><span style="color:#F69D50"> LRUCache</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">public:</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> Key</span><span style="color:#F47067"> =</span><span style="color:#F47067"> int</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> Value</span><span style="color:#F47067"> =</span><span style="color:#F47067"> int</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#DCBDFB">  LRUCache</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">int</span><span style="color:#F69D50"> capacity</span><span style="color:#ADBAC7">) : </span><span style="color:#DCBDFB">capacity_</span><span style="color:#ADBAC7">(capacity) {}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">  Value</span><span style="color:#DCBDFB"> get</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Key</span><span style="color:#F69D50"> key</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">find</span><span style="color:#ADBAC7">(key);</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (it </span><span style="color:#F47067">==</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">end</span><span style="color:#ADBAC7">()) </span><span style="color:#F47067">return</span><span style="color:#F47067"> -</span><span style="color:#6CB6FF">1</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> cache_it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> it->second;</span></span>
<span data-line=""><span style="color:#ADBAC7">    cache_.</span><span style="color:#DCBDFB">move_to_front</span><span style="color:#ADBAC7">(cache_it);</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> cache_it->data.second;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  void</span><span style="color:#DCBDFB"> put</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Key</span><span style="color:#F69D50"> key</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">Value</span><span style="color:#F69D50"> value</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">find</span><span style="color:#ADBAC7">(key);</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (it </span><span style="color:#F47067">!=</span><span style="color:#ADBAC7"> key_to_iter_.</span><span style="color:#DCBDFB">end</span><span style="color:#ADBAC7">()) {</span></span>
<span data-line=""><span style="color:#768390">      // key 已存在，更新</span></span>
<span data-line=""><span style="color:#F47067">      auto</span><span style="color:#ADBAC7"> cache_it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> it->second;</span></span>
<span data-line=""><span style="color:#ADBAC7">      cache_it->data.second </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> value;</span></span>
<span data-line=""><span style="color:#ADBAC7">      cache_.</span><span style="color:#DCBDFB">move_to_front</span><span style="color:#ADBAC7">(cache_it);</span></span>
<span data-line=""><span style="color:#F47067">      return</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // key 不存在，插入新节点</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (key_to_iter_.</span><span style="color:#DCBDFB">size</span><span style="color:#ADBAC7">() </span><span style="color:#F47067">==</span><span style="color:#ADBAC7"> capacity_) {</span></span>
<span data-line=""><span style="color:#768390">      // 容量已满，驱逐尾部节点</span></span>
<span data-line=""><span style="color:#F47067">      auto</span><span style="color:#ADBAC7"> tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> cache_.</span><span style="color:#DCBDFB">tail</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""><span style="color:#ADBAC7">      key_to_iter_.</span><span style="color:#DCBDFB">erase</span><span style="color:#ADBAC7">(tail->data.first);</span></span>
<span data-line=""><span style="color:#ADBAC7">      cache_.</span><span style="color:#DCBDFB">remove</span><span style="color:#ADBAC7">(tail);</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> new_node </span><span style="color:#F47067">=</span><span style="color:#F47067"> new</span><span style="color:#DCBDFB"> Node</span><span style="color:#ADBAC7">({key, value});</span></span>
<span data-line=""><span style="color:#ADBAC7">    key_to_iter_[key] </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> new_node;</span></span>
<span data-line=""><span style="color:#ADBAC7">    cache_.</span><span style="color:#DCBDFB">push_front</span><span style="color:#ADBAC7">(new_node);</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">private:</span></span>
<span data-line=""><span style="color:#F47067">  using</span><span style="color:#F69D50"> KV</span><span style="color:#F47067"> =</span><span style="color:#F69D50"> pair</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">Key</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">Value</span><span style="color:#ADBAC7">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  struct</span><span style="color:#F69D50"> Node</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#ADBAC7">    KV data;</span></span>
<span data-line=""><span style="color:#ADBAC7">    Node </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">prev;</span></span>
<span data-line=""><span style="color:#ADBAC7">    Node </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">next;</span></span>
<span data-line=""><span style="color:#DCBDFB">    Node</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">KV</span><span style="color:#F69D50"> d</span><span style="color:#F47067"> =</span><span style="color:#DCBDFB"> KV</span><span style="color:#ADBAC7">()) : </span><span style="color:#DCBDFB">data</span><span style="color:#ADBAC7">(d) {}</span></span>
<span data-line=""><span style="color:#ADBAC7">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  class</span><span style="color:#F69D50"> LinkedList</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">  public:</span></span>
<span data-line=""><span style="color:#DCBDFB">    LinkedList</span><span style="color:#ADBAC7">() : </span><span style="color:#DCBDFB">dummy_</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">new</span><span style="color:#DCBDFB"> Node</span><span style="color:#ADBAC7">()) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      dummy_->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy_;</span></span>
<span data-line=""><span style="color:#ADBAC7">      dummy_->prev </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy_;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    void</span><span style="color:#DCBDFB"> push_front</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#F69D50">x</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      x->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy_->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      x->prev </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy_;</span></span>
<span data-line=""><span style="color:#ADBAC7">      x->next->prev </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> x;</span></span>
<span data-line=""><span style="color:#ADBAC7">      x->prev->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> x;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    void</span><span style="color:#DCBDFB"> remove</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#F69D50">x</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      x->prev->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> x->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      x->next->prev </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> x->prev;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">    Node</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">tail</span><span style="color:#ADBAC7">() { </span><span style="color:#F47067">return</span><span style="color:#ADBAC7"> dummy_->prev; }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    void</span><span style="color:#DCBDFB"> move_to_front</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">Node</span><span style="color:#F47067"> *</span><span style="color:#F69D50">x</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#DCBDFB">      remove</span><span style="color:#ADBAC7">(x);</span></span>
<span data-line=""><span style="color:#DCBDFB">      push_front</span><span style="color:#ADBAC7">(x);</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  private:</span></span>
<span data-line=""><span style="color:#ADBAC7">    Node </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">dummy_;</span></span>
<span data-line=""><span style="color:#ADBAC7">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  int</span><span style="color:#ADBAC7"> capacity_;</span></span>
<span data-line=""><span style="color:#ADBAC7">  LinkedList cache_;</span></span>
<span data-line=""><span style="color:#ADBAC7">  unordered_map</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">Key, Node </span><span style="color:#F47067">*></span><span style="color:#ADBAC7"> key_to_iter_;</span></span>
<span data-line=""><span style="color:#ADBAC7">};</span></span><button type="button" title="Copy code" aria-label="Copy code" data="class LRUCache {
public:
  using Key = int;
  using Value = int;

  LRUCache(int capacity) : capacity_(capacity) {}

  Value get(Key key) {
    auto it = key_to_iter_.find(key);
    if (it == key_to_iter_.end()) return -1;
    auto cache_it = it->second;
    cache_.move_to_front(cache_it);
    return cache_it->data.second;
  }

  void put(Key key, Value value) {
    auto it = key_to_iter_.find(key);
    if (it != key_to_iter_.end()) {
      // key 已存在，更新
      auto cache_it = it->second;
      cache_it->data.second = value;
      cache_.move_to_front(cache_it);
      return;
    }

    // key 不存在，插入新节点
    if (key_to_iter_.size() == capacity_) {
      // 容量已满，驱逐尾部节点
      auto tail = cache_.tail();
      key_to_iter_.erase(tail->data.first);
      cache_.remove(tail);
    }
    auto new_node = new Node({key, value});
    key_to_iter_[key] = new_node;
    cache_.push_front(new_node);
  }

private:
  using KV = pair<Key, Value>;

  struct Node {
    KV data;
    Node *prev;
    Node *next;
    Node(KV d = KV()) : data(d) {}
  };

  class LinkedList {
  public:
    LinkedList() : dummy_(new Node()) {
      dummy_->next = dummy_;
      dummy_->prev = dummy_;
    }

    void push_front(Node *x) {
      x->next = dummy_->next;
      x->prev = dummy_;
      x->next->prev = x;
      x->prev->next = x;
    }

    void remove(Node *x) {
      x->prev->next = x->next;
      x->next->prev = x->prev;
    }

    Node *tail() { return dummy_->prev; }

    void move_to_front(Node *x) {
      remove(x);
      push_front(x);
    }

  private:
    Node *dummy_;
  };

  int capacity_;
  LinkedList cache_;
  unordered_map<Key, Node *> key_to_iter_;
};" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<hr>
<p>上一篇：<a href="/post/13">LeetCode Hot100 链表篇（上）：评级2~4</a></p>]]></content>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-11-15T16:54:45.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[【写沟-长文】宋后《白泽图》由流行至隐没之原因分析]]></title>
        <id>https://www.tinystar.me/post/3</id>
        <link href="https://www.tinystar.me/post/3"/>
        <updated>2025-11-16T12:12:57.000Z</updated>
        <summary type="html"><![CDATA[作于 2021年春季学期 摘要 宋后，《白泽图》由流行渐至隐没，其背后隐含着中国古代社会文化的巨大变化，而《白泽图》未能适应这种变化，以致被其他事物所取代，逐渐隐没不彰。本文从功能价值与情感价值两大方向展开分析不利于其流传的因素，并将其与辟邪物、佛道教驱邪仪式和后世艺术作品进行比较，提出其隐没原因可能主要有繁复不便、功能单一、仪式感不足与缺乏故事性。 关键词：白泽图；精怪；辟邪文化 一、前言 唐《轩辕皇帝传》1中记载：“（帝）于海滨得白泽神兽，能言，达于万物之情。因问天下鬼神之事，自古精气为物，游魂为变者，凡万一千五百二十种。白泽言之，帝以图写之，以示天下。”这一流传至今的传说，为《白泽图》的...]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>作于 2021年春季学期</p>
</blockquote>
<h2 id="摘要" class="group"><a aria-hidden="true" tabindex="-1" href="#摘要"><span class="icon icon-link"></span></a>摘要</h2>
<p>宋后，《白泽图》由流行渐至隐没，其背后隐含着中国古代社会文化的巨大变化，而《白泽图》未能适应这种变化，以致被其他事物所取代，逐渐隐没不彰。本文从功能价值与情感价值两大方向展开分析不利于其流传的因素，并将其与辟邪物、佛道教驱邪仪式和后世艺术作品进行比较，提出其隐没原因可能主要有繁复不便、功能单一、仪式感不足与缺乏故事性。</p>
<p>关键词：白泽图；精怪；辟邪文化</p>
<h2 id="一前言" class="group"><a aria-hidden="true" tabindex="-1" href="#一前言"><span class="icon icon-link"></span></a>一、前言</h2>
<p>唐《轩辕皇帝传》<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>中记载：“（帝）于海滨得白泽神兽，能言，达于万物之情。因问天下鬼神之事，自古精气为物，游魂为变者，凡万一千五百二十种。白泽言之，帝以图写之，以示天下。”这一流传至今的传说，为《白泽图》的成书增添了几分神秘色彩。《白泽图》载有大量的精怪信息与破解之法，在白泽文化中占据极为重要的地位。通过分析早期文献，可确定其成书年代不晚于东晋，可能成书于东汉中晚期<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup>。随着历史发展，《白泽图》在社会中一度流行，广被征引，又《南史》中记载简文帝著《新増白泽图》五卷,《隋书》、《旧唐书》和《新唐书》中亦均载有“《白泽图》一卷”。而进入宋朝之后，对《白泽图》的征引则明显减少，其书逐渐隐没，终至散佚。<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup>《白泽图》何以从流行转向隐没，其背后是否隐含着社会背景的变迁？学界对《白泽图》的研究主要集中在其对中国古代民俗、宗教和文学的影响等方面，对《白泽图》为何从流行到隐没的研究仍有较多空白。本文首先简单介绍《白泽图》流行的社会背景，再从功能价值与情感价值两个方面对其不利于流传的特点进行分析，主要结合社会背景变迁、其取代者以及其他妖怪题材的艺术作品，试对其隐没做出解释。</p>
<h2 id="二白泽图流行的背景与原因" class="group"><a aria-hidden="true" tabindex="-1" href="#二白泽图流行的背景与原因"><span class="icon icon-link"></span></a>二、《白泽图》流行的背景与原因</h2>
<p>从《白泽图》现存七十余条佚文<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref aria-describedby="footnote-label">4</a></sup>来看，其内容具有固定的模式，主要由“某物之精”、“其状如何”、“名为何物”、“如何抗御或利用”组成。例如：“火之精名宋无忌。持炬火。家人无故失火者，以其名呼之。著绛緷赤，留项后。”常见的应对方法主要有“呼其名”、“食之”和“杀之”，而其中“呼其名”又是占比最多的，明显多于其他。由此可见，《白泽图》蕴含着“呼名可以辟邪”的思想，这是古人信奉名字巫术的体现。名字巫术，是一种“夺取对方名字进而打败/支配/征服/驯服对方”的巫术。这里的名字不再是“文明时代所说的对一个人或事物的能指”，而是巫术时代保留下来的“对于名字拥有者本源的/恒定的/理式的仪式性描述符号”。<sup><a href="#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref aria-describedby="footnote-label">5</a></sup></p>
<p>信奉名字巫术的人们自然选择了用它来驱鬼辟邪。战国秦汉时期，基于名字巫术的驱鬼方法不断发展，从一开始的“呼神驱鬼”，逐渐演变到“直呼鬼之名驱鬼”，反映了古人自我意识和自信力的增强<sup><a href="#user-content-fn-6" id="user-content-fnref-6" data-footnote-ref aria-describedby="footnote-label">6</a></sup>。而《白泽图》的成书就在这一时期的末尾。随后的魏晋南北朝时期则涌现了一些专记鬼怪之名的书籍，除《白泽图》外，还有《百鬼录》、《九鼎记》和《女青鬼律》等。如《女青鬼律》卷一言：“见吾秘经，知鬼姓名皆吉，万鬼不干，千神宾伏。”由此可见，《白泽图》的成书与随后的流行，与当时信奉“名字巫术”、认可呼名驱鬼的社会背景是有着密切关系的。</p>
<p>但随着时代的发展，“呼名驱鬼”体现出了一系列弊端，未能适应社会背景的变化，呈现出过时之势。“呼名驱鬼”作为《白泽图》的思想核心，其没落对《白泽图》的隐没有着重要影响。</p>
<h2 id="三功能价值" class="group"><a aria-hidden="true" tabindex="-1" href="#三功能价值"><span class="icon icon-link"></span></a>三、功能价值</h2>
<h3 id="1繁复与简便之争" class="group"><a aria-hidden="true" tabindex="-1" href="#1繁复与简便之争"><span class="icon icon-link"></span></a>（1）繁复与简便之争</h3>
<p>《白泽图》中主导的“呼名驱鬼”的方法有其天然的不便之处，如内容繁复、适用范围受限，性价比相对不高。</p>
<p>唐张彦远在《历代名画记·第三卷·述古之秘画珍图》中记载：“白泽图一卷，三百二十事，出《抱朴子》，黄帝巡东海而遇之。”由此观之，历史上的《白泽图》共有三百余条记载（若据相关传说，则是一万余条）。</p>
<p>这一数量已经颇多，但另一方面，这些记载对精怪的覆盖范围却实则极其有限。《白泽图》中对精怪的描述较为具体，许多精怪的形象、出现场景等都刻画得较为精细，这很大程度上缩小了特定记载适用的范围。如对厕精的一条记载：“厕神名依。衣青衣，持白杖。知其名呼之者除。不知其名则死。”，明确指出了这只厕精的形象等属性。《白泽图》中至少有3条关于厕精的不同记载，却仍未完全解决在“厕”中辟邪的问题，正体现了这种特异性记载每一个精怪的方式的缺陷。</p>
<p>单条记载的适用范围小，要充分满足驱邪需求，势必需要对精怪加以增补，增加更多的记载。历史上对《白泽图》的增补，仅看载入史册的便有简文帝著《新増白泽图》五卷、《白泽地镜》等。更兼其他记录精怪信息的书籍亦载有大量《白泽图》中所没有的精怪，总量不可小觑。对使用者来说，这是一个不小的负担。</p>
<p>而历代以来，精怪种类呈现越来越多的趋势<sup><a href="#user-content-fn-7" id="user-content-fnref-7" data-footnote-ref aria-describedby="footnote-label">7</a></sup>，宋后各类志怪小说、神魔小说层出不穷，社会的发展创建了大量新生事物，也为新的精怪的诞生提供了土壤。这使“呼名驱鬼”的辟邪方式的这一缺陷更为凸显。若要适应不断发展的辟邪需求，势必需要对精怪加以增补，而这一方面增加了驱鬼时确定精怪名字的负担，另一方面，若各方不断地增补新精怪并取名，也容易引发命名混乱。且对个体来说，实际用到的又仅是大量记载中的一小部分，存在很大的浪费。</p>
<p>由此可见，《白泽图》在使用过程中存在诸多不便。而在这一时期，相对简便的辟邪物成为了发展的趋势，并对“呼名驱鬼”产生了排挤作用。</p>
<p>《中国镇物》一书认为，辟邪物“以有形的器物表达无形的观念，在心理上帮助人们面对各种实际的灾害、危险、凶殃、祸患，以及虚妄的神怪鬼祟。”<sup><a href="#user-content-fn-8" id="user-content-fnref-8" data-footnote-ref aria-describedby="footnote-label">8</a></sup>，是人们求得内心安稳的一种手段。相较于《白泽图》，辟邪物能够提供方便的长期辟邪，且生产传播便利。</p>
<p>自宋朝以后，古代中国已无长期分裂的状态。人们的生活相对平稳，注意力更多地放在了家宅。家宅有其稳定性，需要的是长期辟邪，而非间或驱鬼，简便的辟邪方式更适于民众的需求。《宋元时期的家宅辟邪研究》一文指出，这一时期的辟邪方式愈发与民间融合，更加大众化、娱乐化，并且宋元辟邪的变化在明清两朝获得继承，并更为突出。<sup><a href="#user-content-fn-9" id="user-content-fnref-9" data-footnote-ref aria-describedby="footnote-label">9</a></sup></p>
<p>与需要主动驱鬼的《白泽图》不同，辟邪物基本无需人的干预，在便捷程度上具有极大优越性，更能使人们方便地获得内心安稳，因而更容易在民间广为流传。以白泽相关辟邪物为例。在《白泽图》流行之时，民间就已出现了“白泽枕”、“白泽之图”等辟邪物；而在《白泽图》隐佚之时，这类白泽衍生出的辟邪物却仍在民间稳定存在<sup><a href="#user-content-fn-10" id="user-content-fnref-10" data-footnote-ref aria-describedby="footnote-label">10</a></sup>。白泽在这里成为了辟邪的符号之一，其自身的故事变得不重要了，只作为人们辟邪愿望的承载者而存在。枕白泽枕、悬白泽之图，这些明显比翻查《白泽图》认定精怪之名再执行驱除方法要便捷许多，也更适合长期的使用。此外，人们使用辟邪物时，还逐渐产生了对美观与娱乐身心的追求<sup><a href="#user-content-fn-11" id="user-content-fnref-11" data-footnote-ref aria-describedby="footnote-label">11</a></sup>，而这方面，作为精怪名录的《白泽图》也存在着天生的劣势。</p>
<p>社会的发展还为辟邪物提供了生产、传播上的简便性。在这段时期里，雕版印刷出现，使得用于辟邪的钟馗画、门神画等价格降低，促进了相应信仰的传播。我国经济重心完成南移，商品经济发展，使桃符等辟邪物都成为了较易获得的商品。商业的发达也推动了辟邪物在全国范围的流传，增强了辟邪习俗的区域同一性。<sup><a href="#user-content-fn-12" id="user-content-fnref-12" data-footnote-ref aria-describedby="footnote-label">12</a></sup>相比之下，《白泽图》作为精怪名录，其增补需要有人专门对新产生的精怪进行搜集整理，一来缺乏其人，二来修改周期长，也难以做到及时更新。虽然雕版印刷减少了印刷成本，但更新之后的重新传播仍然是十分困难的。即使在科技发达的今日，更新后的产品也并不总能迅速传遍旧版本的使用者，遑论古时。在生产、传播的角度上看，《白泽图》也输给了更具简便性的辟邪物。</p>
<p>辟邪物的迅猛发展，客观上看，对《白泽图》起到了不小的排挤作用。在日常无事之时，广为流传的辟邪物已起到了安稳内心的作用，人们也就没有必要费心收藏一份相对繁复而使用不便的《白泽图》了。</p>
<h3 id="2功能的单一与丰富之争" class="group"><a aria-hidden="true" tabindex="-1" href="#2功能的单一与丰富之争"><span class="icon icon-link"></span></a>（2）功能的单一与丰富之争</h3>
<p>《宋元时期的家宅辟邪研究》一文指出，在宋元时期，人们的辟邪观念发生了很大变化，求吉心理逐渐兴起，辟邪物更多的以求吉为目的。这一变化在明清两朝获得继承，并更为突出。<sup><a href="#user-content-fn-13" id="user-content-fnref-13" data-footnote-ref aria-describedby="footnote-label">13</a></sup>由此可见，人们已不满足于单纯的驱除精怪，而是更加重视求吉。</p>
<p>求吉，在《白泽图》中是较为欠缺的。诚然，现存的七十余条《白泽图》佚文中，也存在某些精怪“呼之则吉”的描述，但一方面，这类记载占比极小，所涉及的功能也很有限，不能满足民众祈求丰收、家人平安等等各式各样的愿望。并且另一方面，这种求吉方式都以遇见特定的精怪为前提，对场景也有着严格的限制，过于死板，难以在现实生活中运用，不能满足民众普遍求吉的需求。而相比之下，辟邪物可以根据人们的需求轻易地被附上各种祈福求吉的功能，使用起来也十分简便。</p>
<p>此外，两宋以来，佛、道二教的发展均臻于成熟，排挤民间巫术，占据主流<sup><a href="#user-content-fn-14" id="user-content-fnref-14" data-footnote-ref aria-describedby="footnote-label">14</a></sup>。为了拥有更多信徒，佛、道二教自神其教，声称自己具有超人力的自然神力，除驱鬼之外，还能够祈福，甚至修炼成仙、影响来世等等，迎合了民众各方面的内心需要。以道教为例，如道士所出符箓，宣称“具有无为之功效，既能抵御自然灾害、呼风唤雨又能驱鬼避害；既能保佑人的安全健康，又能成为联系神仙与凡人的媒介”<sup><a href="#user-content-fn-15" id="user-content-fnref-15" data-footnote-ref aria-describedby="footnote-label">15</a></sup>，拥有诸多强大功能。佛经中亦有“能除一切苦”之类大而泛的宣称，有着送子观音等满足人们愿望的事物，其对普通民众的吸引力明显强于内容繁复而功能单一的《白泽图》。</p>
<p>总体上，宋后，人们的辟邪观念发生了很大变化，求吉心理兴起，不满足于单纯的驱除精怪，而希望能实现种种美好生活愿望。在这一社会背景下，《白泽图》单一的功能无法充分满足人们的需求，难以与功能丰富的辟邪物以及自神其教的佛、道二教抗衡。</p>
<h2 id="四情感价值" class="group"><a aria-hidden="true" tabindex="-1" href="#四情感价值"><span class="icon icon-link"></span></a>四、情感价值</h2>
<h3 id="1仪式感" class="group"><a aria-hidden="true" tabindex="-1" href="#1仪式感"><span class="icon icon-link"></span></a>（1）仪式感</h3>
<p>如前文所述，《白泽图》在日常的家宅辟邪等方面主要存在使用不便、功能单一的问题，而在正式的驱邪活动中，其在仪式感的营造上又远不及佛、道二教，不易获取民众信仰。久而久之，这极大地削弱了《白泽图》在辟邪文化中的地位。</p>
<p>仪式，是一种“有程序性，象征性，独特性的行为”<sup><a href="#user-content-fn-16" id="user-content-fnref-16" data-footnote-ref aria-describedby="footnote-label">16</a></sup>。仪式的象征意义远大于实际意义，其营造的隆重气氛可在人们心中唤起神圣、崇高的感情。</p>
<p>《白泽图》中，抗御或利用精怪的方法多为“呼其名”、“食之”和“杀之”，全部都是非常实际、普通的行为，不符合仪式的特点。《白泽图》的这一特点与其作为精怪名录的工具书定位相符，某种意义上正好方便人们执行辟邪的行为。但在追求简便的日常辟邪上，其竞争力已弱于更为便捷的辟邪物；而在严肃、正式的驱邪活动中，这种较为简单的辟邪方式却又显得缺乏仪式感、过于平淡，难以在人们心中唤起神圣、崇高之感，不易让民众信服。</p>
<p>而作为《白泽图》功能的重要取代者，佛、道二教对仪式感要重视得多。</p>
<p>在道教的早期，道教驱邪的方式与《白泽图》的“呼名驱鬼”有一定相似之处。《白泽图》一度被道教视为典籍，《抱朴子》称道士入山常携《白泽图》，且云：“及《白泽图》、《九鼎记》，则众鬼自却”。但随着时代的发展，道教驱鬼的方式已与当初有了很大变化。如宋代道教驱邪，一般流程可概括为:“申状、上奏、变神、请兵、立狱、考召、奏醮”<sup><a href="#user-content-fn-17" id="user-content-fnref-17" data-footnote-ref aria-describedby="footnote-label">17</a></sup>，组织严密，对宋代司法过程多有模仿，声势浩大，仪式感十分强烈。</p>
<p>佛教也和道教有着类似的特点，举行的法事中有着一系列仪式。如规模宏大的水陆法会，以七天为一单元，最长可达四十九天，分外坛和内坛两部分，外坛法师日间唱诵经忏真言，晚间施食饿鬼；内坛设置佛像及多个神牌席位，法师观想及念唱仪文疏表，邀请冥界诸鬼神出席，供奉以食物、鲜花、香油、衣服等供品。如是从而营造出隆重的气氛，使信徒更为信服。</p>
<p>佛、道二教的一系列驱邪活动具有很强的仪式感，从而更好地吸收了民众的信仰。而《白泽图》与其背后的“呼名驱鬼”方式仪式感较弱，很难与佛、道二教争夺在民众的信仰。并且在日常的家宅辟邪中，《白泽图》又难以撼动辟邪物的地位，综合起来导致《白泽图》逐渐在辟邪方面失去了用武之地。</p>
<h3 id="2艺术角度" class="group"><a aria-hidden="true" tabindex="-1" href="#2艺术角度"><span class="icon icon-link"></span></a>（2）艺术角度</h3>
<p>在偏实用的辟邪方面，《白泽图》已被很大程度上取代；而在艺术角度上看，《白泽图》也有其不利于传播的一面。</p>
<p>首先是白泽的形象。一来，除了“黄帝遇白泽而制《白泽图》”的传说之外，基本找不到以白泽为主角的故事，白泽在民间故事中几无存在感，严重影响了民众对白泽的熟悉亲近。二来，白泽的原型与形貌均不明确<sup><a href="#user-content-fn-18" id="user-content-fnref-18" data-footnote-ref aria-describedby="footnote-label">18</a></sup>，客观上为二次创作造成了不便。</p>
<p>两宋期间文教发达，文人画家喜好作诗绘画，辟邪之物也成为他们创作的题材。以钟馗为代表的辟邪神人物形象日益丰满，故事情节日益丰富，生活气息浓郁，凶厉之气大减，整体趋向世俗化平民化<sup><a href="#user-content-fn-19" id="user-content-fnref-19" data-footnote-ref aria-describedby="footnote-label">19</a></sup>。然而白泽却在这个过程中近乎隐匿，这不能不说是白泽与《白泽图》的一大遗憾。缺乏后世高质量的内容扩充与再创作，是白泽的形象传播逐渐式微的一大原因。此消彼长之下，《白泽图》在民间的传播则陷入了更为不利的境地。</p>
<p>其次，《白泽图》的内容未能迎合妖怪人性化、妖怪相关记载故事化的趋势。</p>
<p>妖怪形象的演变，有着“性格越来越复杂”、“越来越具有人的喜怒哀乐” <sup><a href="#user-content-fn-20" id="user-content-fnref-20" data-footnote-ref aria-describedby="footnote-label">20</a></sup>的趋势。而《白泽图》内容太单一，基本固定在“某物之精”、“其状如何 ”、“名为何物 ”、“如何应对”的框架里，并不注重对妖怪人性化的一面进行进一步刻画。《白泽图》的定位是记录“精气为物，游魂为变者”，“以示天下”，这种工具书式的记载方式与妖怪文化的发展趋势是有所冲突的。</p>
<p>宋后艺术作品中小说增多，关于妖怪的文献记载多以文学作品作为载体出现，多以故事形式流传。如《子不语》、《聊斋志异》和《剪灯新话》，均以故事生动为一大特点。又如《太平广记》中对超自然的生物进行分类，收罗广泛，而其中仅《诸葛恪》、《陆敬叔》、《张翰》三处征引《白泽图》，且均以讲述故事的形式出现，足见当时人们对故事性的看重。</p>
<p>《论中国妖怪研究的现状》一文又提出，志怪小说中有很大一部分还停留在“现象类妖怪”的阶段，即只记载了某怪异事件的发生，但往往对怪异事件的“原因”是什么不甚重视。<sup><a href="#user-content-fn-21" id="user-content-fnref-21" data-footnote-ref aria-describedby="footnote-label">21</a></sup>意不在妖，重在故事，那《白泽图》记载的大量妖怪之名，反而是当时的人们相对所不看重的。故事性的缺失，使《白泽图》在其辟邪功能被取代后，不易以民众喜闻乐见的文学作品的形式流传，很可能也是其隐没乃至散佚的一大原因。</p>
<h2 id="五结语" class="group"><a aria-hidden="true" tabindex="-1" href="#五结语"><span class="icon icon-link"></span></a>五、结语</h2>
<p>《白泽图》由流行至隐没，其背后隐含着宋后中国古代社会文化的巨大变化，而《白泽图》未能适应这种变化，以致被其他事物所取代，逐渐隐没不彰。</p>
<p>内容上，首先，《白泽图》单条记载的适用范围小，在宋后新的精怪层出不穷的情况下，“呼名驱鬼”的辟邪方式的使用不便问题在日常辟邪中愈加凸显，呈过时之势。其次，《白泽图》未能顺应宋后辟邪观念的变化，不能充分满足人们辟邪之外的丰富需求。而在形式上，《白泽图》“呼名驱鬼”的辟邪方式缺乏仪式感，在正式的驱邪活动中不占优势，不易吸引民众信仰。艺术性的角度上看，《白泽图》的记叙方式倾向于工具书风格，而缺乏故事性，白泽自身也缺乏人性化的故事，在宋后妖怪人性化、妖怪相关记载故事化的大趋势下难以吸引大众，也使《白泽图》不易以民众喜闻乐见的文学作品的形式流传。</p>
<p>值得注意的是，即使在其书隐没之后，《白泽图》确立的“物老成精”等精怪意识，仍然对后世社会文化产生了极为深远的影响，白泽枕、白泽之图等辟邪物也在后世流传甚广<sup><a href="#user-content-fn-22" id="user-content-fnref-22" data-footnote-ref aria-describedby="footnote-label">22</a></sup><sup><a href="#user-content-fn-23" id="user-content-fnref-23" data-footnote-ref aria-describedby="footnote-label">23</a></sup><sup><a href="#user-content-fn-24" id="user-content-fnref-24" data-footnote-ref aria-describedby="footnote-label">24</a></sup>，这些都是我们所不应当忽略的。《白泽图》作为我国古代辟邪文化的重要代表，研究其在不同时期的发展与影响，对揭示我国古代社会思想文化的变迁有着重要意义。</p>
<h1 id="陈述信" class="group"><a aria-hidden="true" tabindex="-1" href="#陈述信"><span class="icon icon-link"></span></a>陈述信</h1>
<p>本文的主题是“宋后《白泽图》由流行至隐没之原因”。这个选题源自短文写作中我产生的一个问题，我对此颇感兴趣，但当时在短文中未能深入讨论。选题时其实还有其他一些想选的题目，但我斟酌之后最终选择了当前的主题。选好之后，因为这个选题还算比较明确，所以在写作过程中没有太多主题上的变化，一直保持到现在的终稿。</p>
<blockquote>
<p>当时考虑过的一些选题思路：</p>
<ul>
<li>中外传统妖怪主题作品对比</li>
<li>妖怪形象与其传播的关系</li>
<li>白泽文化的具体社会影响</li>
<li>白泽图的“出现”或“式微”与社会文化的关系</li>
<li>白泽图、山海经的区别与共性</li>
</ul>
</blockquote>
<p>在撰写、修改长文的过程中，我约了2次写作助理，一次是在第一稿完成、初稿提交前，一次是在终稿提交前。第一次，写作助理带着我一起思考如何修改文章的结构以使之更加清晰；第二次，写作助理主要在一些具体表达上为我提供了建议，比如把“内容/形式”改成“功能价值/情感价值”。这一变化一定程度上体现了我文章结构的改进，这是我感到最有成就感的地方。</p>
<p>除此之外，在写作中，踏足于“前人所未及”的领域、提出并使用充分的论据来支持自己的观点的这一“开拓感”也给我带来了很大的成就感。在引用自己的短文终稿时，还有一点小自得。</p>
<p>写作过程中，个人感觉最有挑战性的地方是如何避免“主观臆断”。比如第一稿中，我所使用的论据其实比终稿少很多，主要靠自己的逻辑推理来进行论证，但过上几天再读就觉得会不会过于想当然了。于是我继续查阅资料，改进了论证过程，添加了不少论据，比如说“《白泽图》中对厕精的记载”、“宋后妖怪相关记载的形式”。我认为改完之后的论证有力了许多。</p>
<p>在本学期，我第一次体验了“准学术”类的写作。虽然过程颇为费劲，但回想起来还是很有趣的经历。经过短文、长文的折腾，文章结构的重要性对我来说已经刻骨铭心了。其他写作上的重要感悟还有选题的“小、清、新”、“论证要避免想当然”等地方，这些都是曾经的自己容易疏忽的。当然，和组员们一起分享阅读感想（吐槽）、一起辩论、一起讨论各种东西等等也是很棒的体验！</p>
<p>2021.06.12</p>
<hr>
<p>上一篇：<a href="/post/2">【写沟-短文】白泽文化在中国古代的历史演变</a></p>
<section data-footnotes class="footnotes"><h2 class="sr-only group" id="footnote-label"><a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>下文中未注明来源的史料均查自“中国哲学书电子化计划”网站，不再分别注释。 <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>孙文起：《〈白泽图〉与古小说志怪渊源》，《哈尔滨学院学报》，2007年，第10期。 <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>林智鑫：《白泽文化在中国古代的历史演变》，“写作与沟通”课程短文终稿，清华大学，2021年。 <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p>佐佐木聪：《復元白沢図——古代中国の妖怪と辟邪文化》，第二章。<br>
下文中未注明来源的《白泽图》片段均引自本书该章节，不再分别注释。 <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p>王洁：《仪式原型与真名母题 —— 幻想作品中名字的解读》，《宜宾学院学报》，2015年，第15卷第11期。 <a href="#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-6">
<p>贺璐璐：《战国秦汉时期以名驱鬼术的分类及其仪式 —— 以简帛材料为中心的考察》，《宜宾学院学报》，2016年，第16卷第2期。 <a href="#user-content-fnref-6" data-footnote-backref="" aria-label="Back to reference 6" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-7">
<p>熊保莹：《明代志怪传奇小说中的妖怪形象研究》，硕士学位论文，河南大学，2011年。 <a href="#user-content-fnref-7" data-footnote-backref="" aria-label="Back to reference 7" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-8">
<p>陶思炎：《中国镇物》，上海：东方出版中心，2012 年，第 1 页。 <a href="#user-content-fnref-8" data-footnote-backref="" aria-label="Back to reference 8" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-9">
<p>马洪琳：《宋元时期的家宅辟邪研究》，硕士学位论文，江西师范大学，2015年6月。 <a href="#user-content-fnref-9" data-footnote-backref="" aria-label="Back to reference 9" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-10">
<p>林智鑫：《白泽文化在中国古代的历史演变》。 <a href="#user-content-fnref-10" data-footnote-backref="" aria-label="Back to reference 10" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-11">
<p>马洪琳：《宋元时期的家宅辟邪研究》。 <a href="#user-content-fnref-11" data-footnote-backref="" aria-label="Back to reference 11" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-12">
<p>马洪琳：《宋元时期的家宅辟邪研究》。 <a href="#user-content-fnref-12" data-footnote-backref="" aria-label="Back to reference 12" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-13">
<p>马洪琳：《宋元时期的家宅辟邪研究》。 <a href="#user-content-fnref-13" data-footnote-backref="" aria-label="Back to reference 13" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-14">
<p>赵容俊：《隋唐宋时代的巫术特征考察》，《中國文化研究》，2010年，第2期。 <a href="#user-content-fnref-14" data-footnote-backref="" aria-label="Back to reference 14" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-15">
<p>赵芃：《道教中的符箓文化》，《中国宗教》，2013年第7期。 <a href="#user-content-fnref-15" data-footnote-backref="" aria-label="Back to reference 15" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-16">
<p>席剑，严开胜：《仪式感研究述评》，《科教导刊 - 电子版（下旬）》，2018年第6期。 <a href="#user-content-fnref-16" data-footnote-backref="" aria-label="Back to reference 16" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-17">
<p>张悦：《宋代道教驱邪模式与世俗政治关系初探》，《史林》，2016年第5期 <a href="#user-content-fnref-17" data-footnote-backref="" aria-label="Back to reference 17" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-18">
<p>何凌霞：《“白泽”考论》，《云梦学刊》，2013年第6期。 <a href="#user-content-fnref-18" data-footnote-backref="" aria-label="Back to reference 18" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-19">
<p>马洪琳：《宋元时期的家宅辟邪研究》。 <a href="#user-content-fnref-19" data-footnote-backref="" aria-label="Back to reference 19" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-20">
<p>熊保莹：《明代志怪传奇小说中的妖怪形象研究》。 <a href="#user-content-fnref-20" data-footnote-backref="" aria-label="Back to reference 20" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-21">
<p>陈昊旻：《论中国妖怪研究的现状》，《愛知論叢》，2021年第2期。 <a href="#user-content-fnref-21" data-footnote-backref="" aria-label="Back to reference 21" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-22">
<p>孙文起：《〈白泽图〉与古小说志怪渊源》。 <a href="#user-content-fnref-22" data-footnote-backref="" aria-label="Back to reference 22" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-23">
<p>周西波：《〈白泽图〉研究》，《中国俗文化研究》，2003年。 <a href="#user-content-fnref-23" data-footnote-backref="" aria-label="Back to reference 23" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-24">
<p>林智鑫：《白泽文化在中国古代的历史演变》。 <a href="#user-content-fnref-24" data-footnote-backref="" aria-label="Back to reference 24" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
        <category label="把多余的事情也探索了吧"/>
        <category label="过去记录"/>
        <category label="人文"/>
        <published>2025-05-12T14:54:01.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[【写沟-短文】白泽文化在中国古代的历史演变]]></title>
        <id>https://www.tinystar.me/post/2</id>
        <link href="https://www.tinystar.me/post/2"/>
        <updated>2025-11-16T10:45:07.000Z</updated>
        <summary type="html"><![CDATA[作于 2021年春季学期 前言 白泽，是我国古代传说中的一种瑞兽，能言语，达于万物之情。相传，黄帝东巡而遇白泽，向它询问天下精怪的信息制成图文，使人们能不受这些精怪的伤害，遂成《白泽图》一书。随着历史发展，《白泽图》以其辟邪功能在社会中流传开来，白泽也逐渐从传说走进社会，以祥瑞、辟邪的形象在朝廷、民间出现。最终，白泽在社会中站稳了脚跟，而一度流行的《白泽图》却散佚了。由此可见，白泽文化的演变是一个较为复杂的过程。现有关于白泽的研究多只着眼于该过程的某一部分，而在白泽文化演变的整体认知上略显不足。由于白泽传说、《白泽图》和白泽衍生文化在演变时并不完全同步，本文将从“白泽传说”、“《白泽图》”和“...]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>作于 2021年春季学期</p>
</blockquote>
<h2 id="前言" class="group"><a aria-hidden="true" tabindex="-1" href="#前言"><span class="icon icon-link"></span></a>前言</h2>
<p>白泽，是我国古代传说中的一种瑞兽，能言语，达于万物之情。相传，黄帝东巡而遇白泽，向它询问天下精怪的信息制成图文，使人们能不受这些精怪的伤害，遂成《白泽图》一书。随着历史发展，《白泽图》以其辟邪功能在社会中流传开来，白泽也逐渐从传说走进社会，以祥瑞、辟邪的形象在朝廷、民间出现。最终，白泽在社会中站稳了脚跟，而一度流行的《白泽图》却散佚了。由此可见，白泽文化的演变是一个较为复杂的过程。现有关于白泽的研究多只着眼于该过程的某一部分，而在白泽文化演变的整体认知上略显不足。由于白泽传说、《白泽图》和白泽衍生文化在演变时并不完全同步，本文将从“白泽传说”、“《白泽图》”和“白泽衍生文化”三个方面，对中国古代的白泽文化进行梳理、分析，以期呈现一个较完整的白泽文化演变流程。</p>
<h2 id="白泽传说" class="group"><a aria-hidden="true" tabindex="-1" href="#白泽传说"><span class="icon icon-link"></span></a>白泽传说</h2>
<p>迄今所知最早描述白泽的文献，出自清《渊鉴类函》卷四三二“白泽”条引《山海经》云：“东望山有兽，名日白泽，能言语。王者有德．明照幽远则至。”此卷又引《黄帝内经》：“黄帝巡守，东至海，登桓山，于海滨得白泽神兽，能言，达于万物之情。因问天下鬼神之事，自古及今，精气为物、游魂为变者，凡万一千五百二十种，白泽言之，帝令以图写之，以示天下，乃作辟邪之文以记之。”现有文献多据此认为白泽首次出现是在《山海经》<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>。</p>
<p>但此处清籍之征引未必可信。清前古籍言及白泽传说时<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup>，均不见征引《山海经》、《黄帝内经》等三国前书籍，而晚至清代、年代差距更远的二书却忽作征引，严重缺乏其他文献佐证。此外，现存宋淳熙本《山海经》远早于清朝，而《渊鉴类函》成书距今不过300年，其作者所见版本未必比今本完整。综上，这些片段有较大可能属误引，可信度相对不足。</p>
<p>若只考虑直接记载，则可为白泽传说的出现时间划出可靠下限。目前已知的最早直接记载为东晋《抱朴子》中的“黄帝穷神奸则记白泽之辞”；由此可见白泽能通晓鬼神，但这里尚未提及“黄帝遇白泽”的传说。南北朝《宋书·符瑞志》记载白泽：“泽兽，黄帝时巡守，至于东滨，泽兽出，能言，达知万物之精，以戒于民，为时除害。贤君明德幽远则来。”这为"黄帝遇白泽"传说的出现时间划了下限。更完整的版本最早见于唐《轩辕皇帝传》：“（帝）于海滨得白泽神兽，能言，达于万物之情。因问天下鬼神之事，自古精气为物，游魂为变者，凡万一千五百二十种。白泽言之，帝以图写之，以示天下。”记载了《宋书·符瑞志》中未提及的妖怪总数，并强调“帝以图写之”，解释了《白泽图》的来源。由此判断，白泽传说至晚在唐时已形成今日模样。</p>
<p>易看出，直接记载给出的时间下限，远晚于根据清籍征引所得。更精确的结论还有待进一步考证。</p>
<p>但不管是相信清籍征引<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup>，还是只着眼于直接记载，都可看出白泽传说存在一个变化的过程，与黄帝结合后定型为今日我们所知的“黄帝遇白泽而制《白泽图》”的故事。</p>
<h2 id="白泽图" class="group"><a aria-hidden="true" tabindex="-1" href="#白泽图"><span class="icon icon-link"></span></a>《白泽图》</h2>
<p>《白泽图》载有大量的精怪信息与破解之法，在白泽文化中占据重要地位。关于其成书年代，《〈白泽图〉与古小说志怪渊源》对《汉书·艺文志》与晋《抱朴子》的内容进行分析，得出其成书于东汉中晚期<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref aria-describedby="footnote-label">4</a></sup>的结论，过程较严谨可信。</p>
<p>《抱朴子》称道士入山常携《白泽图》，且云：“及《白泽图》、《九鼎记》，则众鬼自却”。由此判断，东晋时《白泽图》已成书，并在道士中广泛流传。当时的志怪作品亦对其有所引用，如《搜神记》中诸葛恪破解山精傒囊与陆敬叔识树精彭侯，均以《白泽图》为依据。</p>
<p>而随时间推移，《白泽图》很快被统治阶级接纳：南北朝《宋史》为目前已知最早记载《白泽图》来源传说的史书，《南史》中更有简文帝著《新増白泽图》五卷的记载。隋唐时期，《隋书》、《旧唐书》和《新唐书》中亦均载有“《白泽图》一卷”。</p>
<p>在这一过程中，《白泽图》的流传也愈发广泛。征引《白泽图》内容的书籍明显增多，如《玉烛宝典》、《艺文类聚》和《初学记》等。唐佛经《法苑珠林》更大段引用《白泽图》之文，而《白泽图》本是道士们除妖的典籍，说明佛道二教均对《白泽图》十分重视，足见其影响力提升。</p>
<p>但宋朝及之后，《白泽图》逐渐隐没不彰，乃至散佚。对其散佚原因，周西波《〈白泽图〉研究》文末提出可能是道经吸收了《白泽图》的内容与思想并加以发扬，取代了《白泽图》的功能。而对散佚时间的考证，国内研究似存在空白。</p>
<blockquote>
<p>后来就拿这个作为写沟长文的选题了~</p>
</blockquote>
<p>佐佐木聪在《復元白澤図——古代中国の妖怪と辟邪文化》一书中认为，《白泽图》的散佚发生在983年《太平御览》问世与1041年《崇文总目》问世之间，理由是现存《崇文总目》中没有《白泽图》的相关记载<sup><a href="#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref aria-describedby="footnote-label">5</a></sup>。此见解不无道理，但《崇文总目》年代已远，不能排除现存版本或有残缺的可能。此外，还有一些证据对此说不利。如《宋史》成书于元朝，而其《艺文五》中，仍载“《白泽图》一卷”；明《本草纲目》亦多处直接引用《白泽图》。这些可能表明当时《白泽图》尚未完全散佚。</p>
<p>《元史》、《明史》中均未收录《白泽图》，然《元史》不录艺文、《明史》只录明朝著述，均无法用于判断《白泽图》是否散佚。</p>
<blockquote>
<p>本来想，如果没有的话，就能证明当时《白泽图》已散佚，从而把区间末端往前提上几百年；如果有的话，就能证明当时《白泽图》未散佚，从而把区间起始端往后移上几百年；横竖不亏。结果如意算盘完全落空了。</p>
</blockquote>
<p>综上，《白泽图》的散佚可能发生在宋朝到清朝期间，尚无确凿证据缩小区间。但可确定的是，宋朝之后，对《白泽图》的征引就变得极为稀少，可见即使其尚未散佚，在社会上的影响力也已明显下降。也许可以认为，《白泽图》的散佚正是长期影响力下降的最终结果。</p>
<h2 id="白泽衍生文化" class="group"><a aria-hidden="true" tabindex="-1" href="#白泽衍生文化"><span class="icon icon-link"></span></a>白泽衍生文化</h2>
<p>白泽传说中，白泽遇黄帝而出，代表“王者有德”，使白泽成为朝廷中祥瑞的象征；《白泽图》记载了种种精怪与破解之法，可供辟邪，使白泽自身也带上了辟邪的特性。</p>
<p>首先看“祥瑞”。自唐始，白泽形象便在朝廷的旗帜、官服等物中频频出现。如《旧唐书》有“凡车驾出入，则率其属以清游队，建白泽、朱雀等旗队先驱”的记载；《新唐书·志第十四·车服》中有“诸卫大将军、中郎将以下给袍者，皆易其绣文：……领军卫以白泽，金吾卫以辟邪”的记载。《宋史》、《元史》、《明史》和《明会典》等书中均多处提及白泽旗等白泽形象在朝廷中的使用，其内容基本相近，不曾因改朝换代而间断，整体较为稳定。</p>
<p>而随《白泽图》的流传，白泽自身也带上了辟邪能力。南北朝《北史·张衮传》中就有“白泽字钟葵”之记录。与“钟葵”这一辟邪驱鬼的形象并列，可见白泽能辟邪的观念此时已流传开来。</p>
<p>唐时，则有明确证据体现白泽辟邪文化对民俗的影响。如《旧唐书》中有韦庶人妹七姨“为豹头枕以辟邪，白泽枕以辟魅，伏熊枕以宜男”的记载。敦煌愿文伯二五六九(背面)“儿郎伟”驱傩词日：“驱傩之法，自昔轩辕，锺馗白泽，统领居仙。”<sup><a href="#user-content-fn-6" id="user-content-fnref-6" data-footnote-ref aria-describedby="footnote-label">6</a></sup>将白泽与钟馗并列。《鉴诫录》中又有罗隐以“白泽遭钉钉在门”应顾云的“青蝇被扇扇离座”的故事，说明唐时有在门上钉白泽图像的风俗。</p>
<blockquote>
<p>“融入社会礼俗”这一点没有明确点出。在文章中的结构地位最好更清晰一点。白泽形象成为辟邪的符号，其自身的故事、能力不重要了。</p>
</blockquote>
<p>这种用白泽图像（又称“白泽之图”）辟邪的风俗在后世影响甚广。如宋释道原《景德 传灯录》中称：“家有白猎(泽)之图，必无如是妖怪”。<sup><a href="#user-content-fn-7" id="user-content-fnref-7" data-footnote-ref aria-describedby="footnote-label">7</a></sup>《岁时杂记·赵桃版》、《梦梁录·卷三·五月》中亦有人们绘制白泽图像辟邪的描述。明清时，有《续藏经》：“师曰：‘家有白泽之图，必无如是妖怪。’﹝保福别云：‘家无白泽之图，亦无如是妖怪。’﹞”。《嘉兴藏》：“饥餐渴饮无余事，何用高悬白泽图。”虽然它们对白泽之图并不十分推崇，但足以体现在当时，悬挂白泽之图在民间确是一种普遍现象。</p>
<p>除此之外，明清文学作品中也出现了白泽。如清《斩鬼传》中，白泽乃钟馗坐骑，但其前世乃是罪人伯嚭。《西游记》中亦有“白泽狮”，但作为反派存在。这些均与白泽原本的瑞兽形象大相径庭，可见明清时期白泽在民间形象有所下降。</p>
<p>结合此时《白泽图》很可能已散佚的事实，可见人们对白泽文化背后的白泽传说等已不甚在意，只将悬挂白泽之图等风俗作为习惯流传，而“对通晓鬼神的神兽的尊敬”则明显淡化。</p>
<h2 id="结语" class="group"><a aria-hidden="true" tabindex="-1" href="#结语"><span class="icon icon-link"></span></a>结语</h2>
<p>白泽文化可大致分为白泽传说、《白泽图》和白泽衍生文化三部分，而它们在中国古代历史中的演变并不同步。本文分别对其演变过程进行了梳理分析。白泽传说经过了一个与黄帝结合的过程，而至迟在唐朝定型为“黄帝遇白泽而制《白泽图》”的故事。《白泽图》成书于汉朝，逐渐在社会中流传，曾拥有较强的影响力，但在宋后逐渐隐没不彰，终至散佚。白泽衍生文化则随白泽传说与《白泽图》的流行而产生、发展，并逐渐融入社会礼俗，在前二者逐渐淡出历史舞台时仍然稳固存在。</p>
<h2 id="参考文献" class="group"><a aria-hidden="true" tabindex="-1" href="#参考文献"><span class="icon icon-link"></span></a>参考文献</h2>
<p>[1] 何凌霞：《"白泽"考论》，《云梦学刊》2013年第6期。</p>
<p>[2] 周西波：《〈白泽图〉研究》，《中国俗文化研究》2003年。</p>
<p>[3] 孙文起：《〈白泽图〉与古小说志怪渊源》，《哈尔滨学院学报》2007年第10期。</p>
<h1 id="陈述信" class="group"><a aria-hidden="true" tabindex="-1" href="#陈述信"><span class="icon icon-link"></span></a>陈述信</h1>
<p>本文的主题是白泽文化在中国古代的历史演变。一开始对“白泽”产生兴趣后，我查阅了相关资料与多篇相关文献，发现白泽对中国古代社会还是颇有影响的，于是认为这个题目应该是有东西可写的。了解白泽的过程中，我对一些现象格外感兴趣，比如：白泽传说是怎么演变过来的，“黄帝遇白泽”的故事一开始就有吗？《白泽图》那么流行，之后怎么就散佚了呢？我对《西游记》比较熟悉，而《西游记》中有一只狮精就叫“白泽”，这和白泽“瑞兽”的形象具有相当大的反差，当时也使我感到困惑。在有了这些想要解决的问题之后，我便将这个选题初步确定了下来。</p>
<p>撰写初稿时，我收集了大量史料，将它们组织起来时很自然地选择了按时间线整理的方式。而我又要用它们来解答不同问题，于是很多问题的答案散乱地分布在不同朝代的部分中，对读者很不友好。于是修改时，我采用了类似于“问题导向”的思路，把相对可以独立阐述的几个部分——白泽传说、《白泽图》、衍生文化分开阐述，确实对提高可读性有很大帮助。此外，对一些撰写初稿时论证颇显含糊之处，我进行了更仔细严谨的考证，例如《白泽图》的散佚、清代记载中的征引是否可信。</p>
<p>本文有意跳出目前学界对白泽文化的研究缺乏整体感的问题，希望较为完整地呈现“白泽文化”的演变。例如，“清代征引是否可信”，很多篇相关文献完全没有考虑这一点。但若从历史全局的角度来看，征引时间差如此之长，又无其他史料佐证，其可信度颇值得怀疑；但也无法断言这些征引必然有误。因此，我对二者都做了分析，并最终得到了“白泽传说存在一个演变的过程”的结论，这一过程对我来说还是很有成就感的。除此之外，对“《白泽图》散佚年代的考证”也是颇让我有成就感的部分。</p>
<p>而最大的挑战来自于开头的撰写与内容的梳理。它们实际上是相似的问题，即思路整理。我专门花了不少时间来整理自己的思路，列出自己想表达的事物，并提取它们的共性，而后再经过多次修改，最终较为顺利地解决了这一困难。</p>
<p>初步完成修改之后，正文部分超过了4000字。我又认真地根据本文主题，把文章反复通读，并尽量精简，最终在不影响文章表达的前提下顺利完成了这一工作，文章语言相对也更凝练了一些。</p>
<p>2021.4.11</p>
<hr>
<p>下一篇：<a href="/post/3">【写沟-长文】宋后《白泽图》由流行至隐没之原因分析</a></p>
<section data-footnotes class="footnotes"><h2 class="sr-only group" id="footnote-label"><a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>如 孙文起：《〈白泽图〉与古小说志怪渊源》，《哈尔滨学院学报》2007年第10期。<br>
周西波：《〈白泽图〉研究》，《中国俗文化研究》2003年。 <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>范围为"<a href="https://ctext.org/zhs" rel="nofollow noopener noreferrer" target="_blank">中国哲学书电子化计划</a>"所存古籍。下文史料范围同此。 <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>《山海经》成书年代为战国到秦汉初。《黄帝内经》中的学术理论框架可能奠定于秦，以"黄帝"名号再次编纂的工作可能完成于东汉，而"黄帝遇白泽"的故事当属后者。《春秋合诚图》为汉籍。<br>
由此判断，《黄》、《春》二书的相关记载晚于《山海经》数百年，可佐证"白泽传说有一个发展的过程"的观点（假设清籍征引可靠）。<br>
万群：《从汉语史角度看〈山海经〉的成书年代》，《中国典籍与文化》2013年第2期。<br>
张维波，高也陶，李宏彦：《〈黄帝内经〉成书年代解析》，《中华医史杂志》2017年第3期。 <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p>孙文起：《〈白泽图〉与古小说志怪渊源》，《哈尔滨学院学报》2007年第10期。 <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p>佐佐木聪：《復元白沢図——古代中国の妖怪と辟邪文化》，第二章《白泽图》相关年表。 <a href="#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-6">
<p>文化部文物局古文献研究室编《出土文献研究》，北京：文物出版社，1985年，178页。 <a href="#user-content-fnref-6" data-footnote-backref="" aria-label="Back to reference 6" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-7">
<p>《大正新修大藏经·史传部类》，大藏出版社，1936年，第51册，经号2076，第331页 <a href="#user-content-fnref-7" data-footnote-backref="" aria-label="Back to reference 7" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
        <category label="把多余的事情也探索了吧"/>
        <category label="过去记录"/>
        <category label="人文"/>
        <published>2025-05-12T14:45:17.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[LeetCode Hot100 链表篇（上）：评级2~4]]></title>
        <id>https://www.tinystar.me/post/13</id>
        <link href="https://www.tinystar.me/post/13"/>
        <updated>2025-11-16T10:34:55.000Z</updated>
        <summary type="html"><![CDATA[组合父条目： LeetCode Hot100 链表篇（上）强调对链表本身的熟练掌握。 相交链表（评级2） 找出两条 单链表 相交的起始节点。若不相交，则返回 null. 前条件：保证无环。 后条件：链表必须保持其原始结构。 思路 将2个链表独有部分的长度分别记作 x, y，后续的公共部分的长度记作 z. 考虑双指针一起前进。 最简单的情况，若 x == y，则显然双指针相遇之处就是2条链表的相交之处。 若 x != y，则可在走到尽头时就跳到另一条链表的开头，这样就像是把2条链表的独有部分拼到一起，则有 x + z + y == y + z + x. 若两条链表不相交，即上述算法 z == 0...]]></summary>
        <content type="html"><![CDATA[<p>组合父条目：</p>
<ul>
<li><a href="/post/11/leetcode-hot100">LeetCode Hot100</a></li>
</ul>
<hr>
<p>链表篇（上）强调对链表本身的熟练掌握。</p>
<h2 id="相交链表评级2" class="group"><a aria-hidden="true" tabindex="-1" href="#相交链表评级2"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/intersection-of-two-linked-lists/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">相交链表</a>（评级2）</h2>
<p>找出两条 单链表 相交的起始节点。若不相交，则返回 <code>null</code>.</p>
<p>前条件：保证无环。</p>
<p>后条件：链表必须保持其原始结构。</p>
<details>
<summary>
思路
</summary>
<p>将2个链表独有部分的长度分别记作 <code>x</code>, <code>y</code>，后续的公共部分的长度记作 <code>z</code>.</p>
<p>考虑双指针一起前进。</p>
<p>最简单的情况，若 <code>x == y</code>，则显然双指针相遇之处就是2条链表的相交之处。</p>
<p>若 <code>x != y</code>，则可在走到尽头时就跳到另一条链表的开头，这样就像是把2条链表的独有部分拼到一起，则有 <code>x + z + y == y + z + x</code>.</p>
<p>若两条链表不相交，即上述算法 <code>z == 0</code> 的情况，双指针会停在对方链表的尽头，都是 <code>nullptr</code>，可视作是“在空节点相交”，符合题意。故无需特别处理。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067">*</span><span style="color:#DCBDFB"> getIntersectionNode</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067">*</span><span style="color:#F69D50"> headA</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">ListNode</span><span style="color:#F47067">*</span><span style="color:#F69D50"> headB</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode</span><span style="color:#F47067">*</span><span style="color:#ADBAC7"> p </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> headA;</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode</span><span style="color:#F47067">*</span><span style="color:#ADBAC7"> q </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> headB;</span></span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#ADBAC7"> (p </span><span style="color:#F47067">!=</span><span style="color:#ADBAC7"> q) {</span></span>
<span data-line=""><span style="color:#ADBAC7">        p </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> p </span><span style="color:#F47067">?</span><span style="color:#ADBAC7"> p->next </span><span style="color:#F47067">:</span><span style="color:#ADBAC7"> headB;</span></span>
<span data-line=""><span style="color:#ADBAC7">        q </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> q </span><span style="color:#F47067">?</span><span style="color:#ADBAC7"> q->next </span><span style="color:#F47067">:</span><span style="color:#ADBAC7"> headA;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> p;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    ListNode* p = headA;
    ListNode* q = headB;
    while (p != q) {
        p = p ? p->next : headB;
        q = q ? q->next : headA;
    }
    return p;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="反转链表评级3" class="group"><a aria-hidden="true" tabindex="-1" href="#反转链表评级3"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/reverse-linked-list/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">反转链表</a>（评级3）</h2>
<p>反转 单链表。高频基础考题！</p>
<p>注意提供的头节点 <code>head</code> 也保存了数据，也要参与反转。</p>
<details>
<summary>
思路
</summary>
<p>递归思路：把原始链表拆成「头节点」和「剩余全部节点构成的子链表」两部分，把这个子链表反转完了之后，我们只要把原来的头节点 move_to_back 即可。</p>
<p>迭代思路：从头节点开始，不断把新的节点 move_to_front.</p>
</details>
<details>
<summary>
代码
</summary>
<p>递归解法：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">reverseList</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 把原始链表拆成「头节点」和「剩余全部节点构成的子链表」两部分，</span></span>
<span data-line=""><span style="color:#768390">  // 把这个子链表反转完了之后，我们只要把原来的头节点 move_to_back 即可。</span></span>
<span data-line=""><span style="color:#F47067">  if</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">!</span><span style="color:#ADBAC7">(head </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> head->next)) </span><span style="color:#F47067">return</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#ADBAC7">  ListNode </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">new_head </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> reverseList</span><span style="color:#ADBAC7">(head->next);</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""><span style="color:#ADBAC7">  head->next </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> new_head;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *reverseList(ListNode *head) {
  // 把原始链表拆成「头节点」和「剩余全部节点构成的子链表」两部分，
  // 把这个子链表反转完了之后，我们只要把原来的头节点 move_to_back 即可。
  if (!(head &#x26;&#x26; head->next)) return head;

  ListNode *new_head = reverseList(head->next);
  auto tail = head->next;
  tail->next = head;
  head->next = nullptr;
  return new_head;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>迭代解法：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">reverseList</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 从头节点开始，不断把新的节点 move_to_front.</span></span>
<span data-line=""><span style="color:#ADBAC7">  ListNode </span><span style="color:#DCBDFB">dummy</span><span style="color:#ADBAC7">(</span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7">, head);</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (head </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> head->next) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> to_move </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    head->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> to_move->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    to_move->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    dummy.next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> to_move;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *reverseList(ListNode *head) {
  // 从头节点开始，不断把新的节点 move_to_front.
  ListNode dummy(0, head);
  while (head &#x26;&#x26; head->next) {
    auto to_move = head->next;
    head->next = to_move->next;
    to_move->next = dummy.next;
    dummy.next = to_move;
  }

  return dummy.next;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="链表的中间结点评级3" class="group"><a aria-hidden="true" tabindex="-1" href="#链表的中间结点评级3"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/middle-of-the-linked-list/description/" rel="nofollow noopener noreferrer" target="_blank">链表的中间结点</a>（评级3）</h2>
<blockquote>
<p>是其他题的重要Util，显式列为附加题。</p>
</blockquote>
<p>如果有2个中间结点，则返回后一个中间结点。</p>
<details>
<summary>
思路
</summary>
<p>最容易想到也最通用的做法：先遍历一遍求长度，再走指定步数。</p>
<hr>
<p>不过还有系数更小的做法：快慢指针，令快指针的移动速度是慢指针的2倍——匀速直线运动中，位移正比于速度，快指针走到末尾时慢指针恰好走到中间。</p>
<p>思路非常巧妙，但又很容易理解，可谓「难者不会，会者不难」。</p>
<p>延伸：若要求“如果有两个中间结点，则返回前一个中间结点”？若有2个中间结点，则其索引（0-indexed）分别为 <code>(n-1)//2</code> 和 <code>n//2</code>. 故只需让 <code>fast</code> 先走1步.</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">middleNode</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 快慢指针，快指针的移动速度是慢指针的2倍</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head, fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (fast </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> fast->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> slow->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> fast->next->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> slow;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *middleNode(ListNode *head) {
  // 快慢指针，快指针的移动速度是慢指针的2倍
  auto slow = head, fast = head;
  while (fast &#x26;&#x26; fast->next) {
    slow = slow->next;
    fast = fast->next->next;
  }
  return slow;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="回文链表评级3" class="group"><a aria-hidden="true" tabindex="-1" href="#回文链表评级3"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/palindrome-linked-list/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">回文链表</a>（评级3）</h2>
<p>判断 单链表 是否回文。</p>
<details>
<summary>
思路
</summary>
<p>如果是数组的话，双指针分别从开头和结尾相向移动逐个比较即可。但这是单链表，没法快速获取倒一、倒二、倒三节点……</p>
<p>可以先找到 链表的中间节点，然后反转链表的后半部分。</p>
<p>这样就只要双指针去顺序比较两部分链表是否完全一致了。</p>
<p>依赖「<a href="https://leetcode.cn/problems/middle-of-the-linked-list/" rel="nofollow noopener noreferrer" target="_blank">链表的中间结点</a>」和「<a href="https://leetcode.cn/problems/reverse-linked-list/" rel="nofollow noopener noreferrer" target="_blank">反转链表</a>」。一题顶三题！</p>
<p>知道 <strong>存在</strong> 「反转链表」和「链表的中间结点」的算法后就很简单了，不然好像挺难想的😇。因此积累一些 utils 算法还是有价值的。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">class</span><span style="color:#F69D50"> Solution</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">public:</span></span>
<span data-line=""><span style="color:#F47067">  bool</span><span style="color:#DCBDFB"> isPalindrome</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">    // 可以先找到 链表的中间节点，然后反转链表的后半部分</span></span>
<span data-line=""><span style="color:#768390">    // 这样就只要双指针去顺序比较两部分链表是否完全一致了</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 这里假设了允许修改链表结构</span></span>
<span data-line=""><span style="color:#768390">    // 不允许的话，确定答案后不能直接 return，要先把后半部分反转回去</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#ADBAC7">    ListNode </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">mid </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> middleNode</span><span style="color:#ADBAC7">(head);</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">head2 </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> reverseList</span><span style="color:#ADBAC7">(mid);</span></span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#ADBAC7"> (head2) {</span></span>
<span data-line=""><span style="color:#F47067">      if</span><span style="color:#ADBAC7"> (head->val </span><span style="color:#F47067">!=</span><span style="color:#ADBAC7"> head2->val) </span><span style="color:#F47067">return</span><span style="color:#6CB6FF"> false</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">      head </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      head2 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head2->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#6CB6FF"> true</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">private:</span></span>
<span data-line=""><span style="color:#F69D50">  ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">middleNode</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head, fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#ADBAC7"> (fast </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> fast->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> slow->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> fast->next->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> slow;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">  ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">reverseList</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode </span><span style="color:#DCBDFB">dummy</span><span style="color:#ADBAC7">(</span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7">, head);</span></span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#ADBAC7"> (head </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> head->next) {</span></span>
<span data-line=""><span style="color:#F47067">      auto</span><span style="color:#ADBAC7"> to_move </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      head->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> to_move->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      to_move->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      dummy.next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> to_move;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#ADBAC7">};</span></span><button type="button" title="Copy code" aria-label="Copy code" data="class Solution {
public:
  bool isPalindrome(ListNode *head) {
    // 可以先找到 链表的中间节点，然后反转链表的后半部分
    // 这样就只要双指针去顺序比较两部分链表是否完全一致了

    // 这里假设了允许修改链表结构
    // 不允许的话，确定答案后不能直接 return，要先把后半部分反转回去

    ListNode *mid = middleNode(head);
    ListNode *head2 = reverseList(mid);
    while (head2) {
      if (head->val != head2->val) return false;
      head = head->next;
      head2 = head2->next;
    }
    return true;
  }

private:
  ListNode *middleNode(ListNode *head) {
    auto slow = head, fast = head;
    while (fast &#x26;&#x26; fast->next) {
      slow = slow->next;
      fast = fast->next->next;
    }
    return slow;
  }

  ListNode *reverseList(ListNode *head) {
    ListNode dummy(0, head);
    while (head &#x26;&#x26; head->next) {
      auto to_move = head->next;
      head->next = to_move->next;
      to_move->next = dummy.next;
      dummy.next = to_move;
    }

    return dummy.next;
  }
};" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="环形链表评级2" class="group"><a aria-hidden="true" tabindex="-1" href="#环形链表评级2"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/linked-list-cycle/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">环形链表</a>（评级2）</h2>
<p>判断单链表中是否有环。</p>
<details>
<summary>
思路
</summary>
<p>快慢指针，以慢指针为参照物，则快指针每次走1步，满足介值性，因此「有环」等价于「快慢指针相遇」。</p>
<p>总之，是关注2个指针的「相对」位移。思路非常巧妙，但又很容易理解，可谓「难者不会，会者不难」。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">bool</span><span style="color:#DCBDFB"> hasCycle</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 快慢指针</span></span>
<span data-line=""><span style="color:#768390">  // 以慢指针为参照物，则快指针每次走1步，满足介值性</span></span>
<span data-line=""><span style="color:#768390">  // 因此「有环」等价于「快慢指针相遇」</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head, fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (fast </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> fast->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> slow->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> fast->next->next;</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (slow </span><span style="color:#F47067">==</span><span style="color:#ADBAC7"> fast) </span><span style="color:#F47067">return</span><span style="color:#6CB6FF"> true</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#6CB6FF"> false</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="bool hasCycle(ListNode *head) {
  // 快慢指针
  // 以慢指针为参照物，则快指针每次走1步，满足介值性
  // 因此「有环」等价于「快慢指针相遇」
  auto slow = head, fast = head;
  while (fast &#x26;&#x26; fast->next) {
    slow = slow->next;
    fast = fast->next->next;
    if (slow == fast) return true;
  }
  return false;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="环形链表-ii评级2" class="group"><a aria-hidden="true" tabindex="-1" href="#环形链表-ii评级2"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/linked-list-cycle-ii/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">环形链表 II</a>（评级2）</h2>
<p>单链表，不只要判断是否存在环，存在环的话还要找出环的入口。</p>
<details>
<summary>
思路
</summary>
<p>先列式子推导一下看看能推出什么。</p>
<p>设头节点到环的入口的距离为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi></mrow><annotation encoding="application/x-tex">a</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">a</span></span></span></span>，环长为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi></mrow><annotation encoding="application/x-tex">b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span></span></span></span>. 设快慢指针相遇时慢指针走了 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span>，则快指针走了 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mi>x</mi></mrow><annotation encoding="application/x-tex">2x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span><span class="mord mathnormal">x</span></span></span></span>.</p>
<p>快指针比慢指针多走了<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span>，则立得 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi><mi mathvariant="normal">∣</mi><mi>x</mi></mrow><annotation encoding="application/x-tex">b | x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">b</span><span class="mord">∣</span><span class="mord mathnormal">x</span></span></span></span>，记 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mi mathvariant="normal">/</mi><mi>b</mi><mo>=</mo><mo>:</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">x / b =: k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">x</span><span class="mord">/</span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span>，即快指针比慢指针多走了 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span>圈.</p>
<p>慢指针在环内走了 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>−</mo><mi>a</mi><mo>=</mo><mi>k</mi><mi>b</mi><mo>−</mo><mi>a</mi><mo>≡</mo><mo>−</mo><mi>a</mi><mspace></mspace><mspace width="0.4444em"></mspace><mo stretchy="false">(</mo><mrow><mi mathvariant="normal">m</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><mspace width="0.3333em"></mspace><mi>b</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">x-a = kb-a \equiv -a \pmod b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">kb</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4637em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≡</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord mathnormal">a</span><span class="mspace allowbreak"></span><span class="mspace" style="margin-right:0.4444em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord"><span class="mord mathrm">mod</span></span></span><span class="mspace" style="margin-right:0.3333em;"></span><span class="mord mathnormal">b</span><span class="mclose">)</span></span></span></span>，即继续走 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi></mrow><annotation encoding="application/x-tex">a</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">a</span></span></span></span> 步就是环的入口了.</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">detectCycle</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head, fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (fast </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> fast->next) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> slow->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    fast </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> fast->next->next;</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (slow </span><span style="color:#F47067">==</span><span style="color:#ADBAC7"> fast) {</span></span>
<span data-line=""><span style="color:#768390">      // 有环，找环的入口</span></span>
<span data-line=""><span style="color:#768390">      // 推导出的结论：</span></span>
<span data-line=""><span style="color:#768390">      // 设头节点到环的入口的距离为 $a$</span></span>
<span data-line=""><span style="color:#768390">      // 则快慢指针相遇后继续走 $a$ 步就是环的入口了</span></span>
<span data-line=""><span style="color:#F47067">      while</span><span style="color:#ADBAC7"> (slow </span><span style="color:#F47067">!=</span><span style="color:#ADBAC7"> head) {</span></span>
<span data-line=""><span style="color:#ADBAC7">        slow </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> slow->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">        head </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      }</span></span>
<span data-line=""><span style="color:#F47067">      return</span><span style="color:#ADBAC7"> slow;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *detectCycle(ListNode *head) {
  auto slow = head, fast = head;
  while (fast &#x26;&#x26; fast->next) {
    slow = slow->next;
    fast = fast->next->next;
    if (slow == fast) {
      // 有环，找环的入口
      // 推导出的结论：
      // 设头节点到环的入口的距离为 $a$
      // 则快慢指针相遇后继续走 $a$ 步就是环的入口了
      while (slow != head) {
        slow = slow->next;
        head = head->next;
      }
      return slow;
    }
  }
  return nullptr;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="合并两个有序链表评级3" class="group"><a aria-hidden="true" tabindex="-1" href="#合并两个有序链表评级3"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/merge-two-sorted-lists/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">合并两个有序链表</a>（评级3）</h2>
<details>
<summary>
思路
</summary>
<p>就是 <code>merge</code> 的链表版。照惯例创建一个 dummy 头节点来减少分类讨论。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">mergeTwoLists</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">list1</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">list2</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">  ListNode dummy;</span></span>
<span data-line=""><span style="color:#ADBAC7">  ListNode </span><span style="color:#F47067">*</span><span style="color:#ADBAC7">tail </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (; list1 </span><span style="color:#F47067">&#x26;&#x26;</span><span style="color:#ADBAC7"> list2; tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> tail->next) {</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (list1->val </span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7"> list2->val) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list1;</span></span>
<span data-line=""><span style="color:#ADBAC7">      list1 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list1->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    } </span><span style="color:#F47067">else</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#ADBAC7">      tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list2;</span></span>
<span data-line=""><span style="color:#ADBAC7">      list2 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list2->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // 拼接剩余部分</span></span>
<span data-line=""><span style="color:#ADBAC7">  tail->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> list1 </span><span style="color:#F47067">?</span><span style="color:#ADBAC7"> list1 </span><span style="color:#F47067">:</span><span style="color:#ADBAC7"> list2;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *mergeTwoLists(ListNode *list1, ListNode *list2) {
  ListNode dummy;
  ListNode *tail = &#x26;dummy;

  for (; list1 &#x26;&#x26; list2; tail = tail->next) {
    if (list1->val < list2->val) {
      tail->next = list1;
      list1 = list1->next;
    } else {
      tail->next = list2;
      list2 = list2->next;
    }
  }

  // 拼接剩余部分
  tail->next = list1 ? list1 : list2;

  return dummy.next;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="两数相加评级3" class="group"><a aria-hidden="true" tabindex="-1" href="#两数相加评级3"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/add-two-numbers/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">两数相加</a>（评级3）</h2>
<p>2个单链表，各自表示1个整数——逆序存储数位。</p>
<details>
<summary>
思路
</summary>
<p>逆序存储相当于已经帮忙从个位开始对齐了。</p>
<p>模拟竖式计算即可：逐位相加，逢10进1.</p>
<p>延伸：如果是正序存储的话，就先 反转链表 呗。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">addTwoNumbers</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">l1</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">l2</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 模拟竖式计算：逐位相加，逢10进1.</span></span>
<span data-line=""><span style="color:#ADBAC7">  ListNode dummy;</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> tail </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  int</span><span style="color:#ADBAC7"> carry </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (l1 </span><span style="color:#F47067">||</span><span style="color:#ADBAC7"> l2 </span><span style="color:#F47067">||</span><span style="color:#ADBAC7"> carry) {</span></span>
<span data-line=""><span style="color:#F47067">    int</span><span style="color:#ADBAC7"> sum </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> carry;</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (l1) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      sum </span><span style="color:#F47067">+=</span><span style="color:#ADBAC7"> l1->val;</span></span>
<span data-line=""><span style="color:#ADBAC7">      l1 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> l1->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (l2) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      sum </span><span style="color:#F47067">+=</span><span style="color:#ADBAC7"> l2->val;</span></span>
<span data-line=""><span style="color:#ADBAC7">      l2 </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> l2->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> div_res </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> div</span><span style="color:#ADBAC7">(sum, </span><span style="color:#6CB6FF">10</span><span style="color:#ADBAC7">);</span></span>
<span data-line=""><span style="color:#ADBAC7">    carry </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> div_res.quot;</span></span>
<span data-line=""><span style="color:#ADBAC7">    tail->next </span><span style="color:#F47067">=</span><span style="color:#F47067"> new</span><span style="color:#DCBDFB"> ListNode</span><span style="color:#ADBAC7">(div_res.rem);</span></span>
<span data-line=""><span style="color:#ADBAC7">    tail </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> tail->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) {
  // 模拟竖式计算：逐位相加，逢10进1.
  ListNode dummy;
  auto tail = &#x26;dummy;

  int carry = 0;
  while (l1 || l2 || carry) {
    int sum = carry;
    if (l1) {
      sum += l1->val;
      l1 = l1->next;
    }
    if (l2) {
      sum += l2->val;
      l2 = l2->next;
    }
    auto div_res = div(sum, 10);
    carry = div_res.quot;
    tail->next = new ListNode(div_res.rem);
    tail = tail->next;
  }

  return dummy.next;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="删除链表的倒数第-n-个结点评级3" class="group"><a aria-hidden="true" tabindex="-1" href="#删除链表的倒数第-n-个结点评级3"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/remove-nth-node-from-end-of-list/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">删除链表的倒数第 N 个结点</a>（评级3）</h2>
<p>并返回单链表的头结点。</p>
<details>
<summary>
思路
</summary>
<p>要删掉单链表的倒数第 <code>n</code> 个结点，就得找到其倒数第 <code>n+1</code> 个结点。</p>
<p>而要找到倒数第 <code>n+1</code> 个结点，让2个距离为 <code>n+1</code> 的指针相对静止地一起前进即可。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">removeNthFromEnd</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">, </span><span style="color:#F47067">int</span><span style="color:#F69D50"> n</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 找到倒数第 (n+1) 个结点</span></span>
<span data-line=""><span style="color:#ADBAC7">  ListNode </span><span style="color:#DCBDFB">dummy</span><span style="color:#ADBAC7">(</span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7">, head);</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> left </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy;</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> right </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy;</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">int</span><span style="color:#ADBAC7"> _ </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">; _ </span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7"> n </span><span style="color:#F47067">+</span><span style="color:#6CB6FF"> 1</span><span style="color:#ADBAC7">; </span><span style="color:#F47067">++</span><span style="color:#ADBAC7">_) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    right </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> right->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (right) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    right </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> right->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">    left </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> left->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#768390">  // 从而删掉倒数第 n 个结点</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> node_to_delete </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> left->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  left->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> left->next->next;</span></span>
<span data-line=""><span style="color:#F47067">  delete</span><span style="color:#ADBAC7"> node_to_delete;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *removeNthFromEnd(ListNode *head, int n) {
  // 找到倒数第 (n+1) 个结点
  ListNode dummy(0, head);
  auto left = &#x26;dummy;
  auto right = &#x26;dummy;
  for (int _ = 0; _ < n + 1; ++_) {
    right = right->next;
  }
  while (right) {
    right = right->next;
    left = left->next;
  }
  // 从而删掉倒数第 n 个结点
  auto node_to_delete = left->next;
  left->next = left->next->next;
  delete node_to_delete;

  return dummy.next;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="两两交换链表中的节点评级4" class="group"><a aria-hidden="true" tabindex="-1" href="#两两交换链表中的节点评级4"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/swap-nodes-in-pairs/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">两两交换链表中的节点</a>（评级4）</h2>
<p>两两一组，内部交换，例：<code>0->1->2->3->4</code> 变为 <code>1->0->3->2->4</code>.</p>
<details>
<summary>
思路
</summary>
<p>按题意书写。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">swapPairs</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">  // 两两一组，内部交换</span></span>
<span data-line=""><span style="color:#ADBAC7">  ListNode </span><span style="color:#DCBDFB">dummy</span><span style="color:#ADBAC7">(</span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7">, head);</span></span>
<span data-line=""><span style="color:#F47067">  auto</span><span style="color:#ADBAC7"> pre </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy;</span></span>
<span data-line=""><span style="color:#F47067">  while</span><span style="color:#ADBAC7"> (</span><span style="color:#6CB6FF">true</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> first </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> pre->next;</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (first </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">) </span><span style="color:#F47067">break</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> second </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> first->next;</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (second </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">) </span><span style="color:#F47067">break</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> nxt </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> second->next;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 交换</span></span>
<span data-line=""><span style="color:#ADBAC7">    pre->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> second;</span></span>
<span data-line=""><span style="color:#ADBAC7">    second->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> first;</span></span>
<span data-line=""><span style="color:#ADBAC7">    first->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> nxt;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // 移动到下一组</span></span>
<span data-line=""><span style="color:#ADBAC7">    pre </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> first;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="ListNode *swapPairs(ListNode *head) {
  // 两两一组，内部交换
  ListNode dummy(0, head);
  auto pre = &#x26;dummy;
  while (true) {
    auto first = pre->next;
    if (first == nullptr) break;
    auto second = first->next;
    if (second == nullptr) break;
    auto nxt = second->next;

    // 交换
    pre->next = second;
    second->next = first;
    first->next = nxt;

    // 移动到下一组
    pre = first;
  }
  return dummy.next;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="k-个一组翻转链表评级4" class="group"><a aria-hidden="true" tabindex="-1" href="#k-个一组翻转链表评级4"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/reverse-nodes-in-k-group/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">K 个一组翻转链表</a>（评级4）</h2>
<p>是「两两交换链表中的节点」的推广，从「2个一组」推广为了「K个一组」。</p>
<p>分组后剩余的节点保持原有顺序。</p>
<details>
<summary>
思路
</summary>
<p>按题意书写。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">class</span><span style="color:#F69D50"> Solution</span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">public:</span></span>
<span data-line=""><span style="color:#F69D50">  ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">reverseKGroup</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">head</span><span style="color:#ADBAC7">, </span><span style="color:#F47067">int</span><span style="color:#F69D50"> k</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">    // 每 k 个节点为一组，组内反转</span></span>
<span data-line=""><span style="color:#768390">    // 剩余的节点保持原有顺序</span></span>
<span data-line=""><span style="color:#ADBAC7">    ListNode </span><span style="color:#DCBDFB">dummy</span><span style="color:#ADBAC7">(</span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7">, head);</span></span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto</span><span style="color:#ADBAC7"> pre </span><span style="color:#F47067">=</span><span style="color:#F47067"> &#x26;</span><span style="color:#ADBAC7">dummy; </span><span style="color:#DCBDFB">has_k_nodes</span><span style="color:#ADBAC7">(pre, k);) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      pre </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> reverse_next_k_nodes</span><span style="color:#ADBAC7">(pre, k);</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> dummy.next;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">private:</span></span>
<span data-line=""><span style="color:#F47067">  bool</span><span style="color:#DCBDFB"> has_k_nodes</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">dummy</span><span style="color:#ADBAC7">, </span><span style="color:#F47067">int</span><span style="color:#F69D50"> k</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">int</span><span style="color:#ADBAC7"> count </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">; count </span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7"> k; </span><span style="color:#F47067">++</span><span style="color:#ADBAC7">count) {</span></span>
<span data-line=""><span style="color:#ADBAC7">      dummy </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy->next;</span></span>
<span data-line=""><span style="color:#F47067">      if</span><span style="color:#ADBAC7"> (dummy </span><span style="color:#F47067">==</span><span style="color:#6CB6FF"> nullptr</span><span style="color:#ADBAC7">) </span><span style="color:#F47067">return</span><span style="color:#6CB6FF"> false</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#6CB6FF"> true</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">  // return new tail</span></span>
<span data-line=""><span style="color:#F69D50">  ListNode</span><span style="color:#F47067"> *</span><span style="color:#DCBDFB">reverse_next_k_nodes</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">ListNode</span><span style="color:#F47067"> *</span><span style="color:#F69D50">dummy</span><span style="color:#ADBAC7">, </span><span style="color:#F47067">int</span><span style="color:#F69D50"> k</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#768390">    // move_to_front</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> head </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy->next;</span></span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">int</span><span style="color:#ADBAC7"> i </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">; i </span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7"> k </span><span style="color:#F47067">-</span><span style="color:#6CB6FF"> 1</span><span style="color:#ADBAC7">; </span><span style="color:#F47067">++</span><span style="color:#ADBAC7">i) {</span></span>
<span data-line=""><span style="color:#F47067">      auto</span><span style="color:#ADBAC7"> to_move </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> head->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      head->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> to_move->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      to_move->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> dummy->next;</span></span>
<span data-line=""><span style="color:#ADBAC7">      dummy->next </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> to_move;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> head;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#ADBAC7">};</span></span><button type="button" title="Copy code" aria-label="Copy code" data="class Solution {
public:
  ListNode *reverseKGroup(ListNode *head, int k) {
    // 每 k 个节点为一组，组内反转
    // 剩余的节点保持原有顺序
    ListNode dummy(0, head);
    for (auto pre = &#x26;dummy; has_k_nodes(pre, k);) {
      pre = reverse_next_k_nodes(pre, k);
    }
    return dummy.next;
  }

private:
  bool has_k_nodes(ListNode *dummy, int k) {
    for (int count = 0; count < k; ++count) {
      dummy = dummy->next;
      if (dummy == nullptr) return false;
    }
    return true;
  }

  // return new tail
  ListNode *reverse_next_k_nodes(ListNode *dummy, int k) {
    // move_to_front
    auto head = dummy->next;
    for (int i = 0; i < k - 1; ++i) {
      auto to_move = head->next;
      head->next = to_move->next;
      to_move->next = dummy->next;
      dummy->next = to_move;
    }
    return head;
  }
};" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<hr>
<p>下一篇：<a href="/post/14">LeetCode Hot100 链表篇（下）：评级6~7</a></p>]]></content>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-11-14T15:47:58.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[【算法思想】状态转移]]></title>
        <id>https://www.tinystar.me/post/15</id>
        <link href="https://www.tinystar.me/post/15"/>
        <updated>2025-11-15T17:09:50.000Z</updated>
        <summary type="html"><![CDATA[暂时为空，只是占位获取 id 用，给 LeetCode Hot100 的 贪心算法、动态规划、搜索 相应分组引用——覆盖还挺广的…… 之后补上。]]></summary>
        <content type="html"><![CDATA[<p>暂时为空，只是占位获取 id 用，给 LeetCode Hot100 的 贪心算法、动态规划、搜索 相应分组引用——覆盖还挺广的……</p>
<p>之后补上。</p>]]></content>
        <category label="总结笔记"/>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-11-15T17:09:50.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[LeetCode Hot100 哈希篇]]></title>
        <id>https://www.tinystar.me/post/12</id>
        <link href="https://www.tinystar.me/post/12"/>
        <updated>2025-11-14T16:07:39.000Z</updated>
        <summary type="html"><![CDATA[组合父条目： LeetCode Hot100 两数之和（评级2） 给定数组 nums 和一个整数目标值 target. 在该数组中找出和为目标值 target 的那两个整数的下标。 思路 枚举其中一个数，则只需查找 target 减去这个数是否在数组中。使用 hash index 即可在 O(1) 时间完成这个查找。 代码 using Val = int; using Idx = int; vector&#x3C;int> twoSum(vector&#x3C;Val> &#x26;nums, Val target) { int n = nums.size(); unordered_map&#...]]></summary>
        <content type="html"><![CDATA[<p>组合父条目：</p>
<ul>
<li><a href="/post/11/leetcode-hot100">LeetCode Hot100</a></li>
</ul>
<hr>
<h2 id="两数之和评级2" class="group"><a aria-hidden="true" tabindex="-1" href="#两数之和评级2"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/two-sum/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">两数之和</a>（评级2）</h2>
<p>给定数组 <code>nums</code> 和一个整数目标值 <code>target</code>.</p>
<p>在该数组中找出和为目标值 <code>target</code> 的那两个整数的下标。</p>
<details>
<summary>
思路
</summary>
<p>枚举其中一个数，则只需查找 <code>target</code> 减去这个数是否在数组中。使用 hash index 即可在 <code>O(1)</code> 时间完成这个查找。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">using</span><span style="color:#F69D50"> Val</span><span style="color:#F47067"> =</span><span style="color:#F47067"> int</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">using</span><span style="color:#F69D50"> Idx</span><span style="color:#F47067"> =</span><span style="color:#F47067"> int</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F47067">int</span><span style="color:#ADBAC7">> </span><span style="color:#DCBDFB">twoSum</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">Val</span><span style="color:#ADBAC7">> </span><span style="color:#F47067">&#x26;</span><span style="color:#F69D50">nums</span><span style="color:#ADBAC7">, </span><span style="color:#F69D50">Val</span><span style="color:#F69D50"> target</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">  int</span><span style="color:#ADBAC7"> n </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> nums.</span><span style="color:#DCBDFB">size</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#ADBAC7">  unordered_map</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">Val, Idx</span><span style="color:#F47067">></span><span style="color:#ADBAC7"> idx;</span></span>
<span data-line=""><span style="color:#F47067">  for</span><span style="color:#ADBAC7"> (Idx i </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">; i </span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7"> n; </span><span style="color:#F47067">++</span><span style="color:#ADBAC7">i) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    Val num </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> nums[i];</span></span>
<span data-line=""><span style="color:#ADBAC7">    Val comp </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> target </span><span style="color:#F47067">-</span><span style="color:#ADBAC7"> num;</span></span>
<span data-line=""><span style="color:#F47067">    auto</span><span style="color:#ADBAC7"> comp_it </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> idx.</span><span style="color:#DCBDFB">find</span><span style="color:#ADBAC7">(comp);</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> (comp_it </span><span style="color:#F47067">!=</span><span style="color:#ADBAC7"> idx.</span><span style="color:#DCBDFB">end</span><span style="color:#ADBAC7">()) </span><span style="color:#F47067">return</span><span style="color:#ADBAC7"> {comp_it->second, i};</span></span>
<span data-line=""><span style="color:#ADBAC7">    idx[num] </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> i;</span></span>
<span data-line=""><span style="color:#ADBAC7">  }</span></span>
<span data-line=""><span style="color:#F47067">  return</span><span style="color:#ADBAC7"> {};</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="using Val = int;
using Idx = int;

vector<int> twoSum(vector<Val> &#x26;nums, Val target) {
  int n = nums.size();

  unordered_map<Val, Idx> idx;
  for (Idx i = 0; i < n; ++i) {
    Val num = nums[i];
    Val comp = target - num;
    auto comp_it = idx.find(comp);
    if (comp_it != idx.end()) return {comp_it->second, i};
    idx[num] = i;
  }
  return {};
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="字母异位词分组评级4" class="group"><a aria-hidden="true" tabindex="-1" href="#字母异位词分组评级4"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/group-anagrams/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">字母异位词分组</a>（评级4）</h2>
<details>
<summary>
思路
</summary>
<p>字母异位词 &#x3C;=> 排序后相等。</p>
<p>把排序后的字符串作为 map 的 key 即可。</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">string</span><span style="color:#ADBAC7">>> </span><span style="color:#DCBDFB">groupAnagrams</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F69D50">string</span><span style="color:#ADBAC7">></span><span style="color:#F47067">&#x26;</span><span style="color:#F69D50"> strs</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    unordered_map</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">string, vector</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">string</span><span style="color:#F47067">>></span><span style="color:#ADBAC7"> m;</span></span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto&#x26;</span><span style="color:#ADBAC7"> s : strs) {</span></span>
<span data-line=""><span style="color:#F47067">        auto</span><span style="color:#ADBAC7"> sorted_s </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> s;</span></span>
<span data-line=""><span style="color:#F69D50">        ranges</span><span style="color:#ADBAC7">::</span><span style="color:#DCBDFB">sort</span><span style="color:#ADBAC7">(sorted_s);</span></span>
<span data-line=""><span style="color:#ADBAC7">        m[sorted_s].</span><span style="color:#DCBDFB">push_back</span><span style="color:#ADBAC7">(s);</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">    // build ans</span></span>
<span data-line=""><span style="color:#ADBAC7">    vector</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">vector</span><span style="color:#F47067">&#x3C;</span><span style="color:#ADBAC7">string</span><span style="color:#F47067">>></span><span style="color:#ADBAC7"> ans;</span></span>
<span data-line=""><span style="color:#ADBAC7">    ans.</span><span style="color:#DCBDFB">reserve</span><span style="color:#ADBAC7">(m.</span><span style="color:#DCBDFB">size</span><span style="color:#ADBAC7">());</span></span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">auto&#x26;</span><span style="color:#ADBAC7"> [_, value] : m) {</span></span>
<span data-line=""><span style="color:#ADBAC7">        ans.</span><span style="color:#DCBDFB">push_back</span><span style="color:#ADBAC7">(value);</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> ans;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="vector<vector<string>> groupAnagrams(vector<string>&#x26; strs) {
    unordered_map<string, vector<string>> m;
    for (auto&#x26; s : strs) {
        auto sorted_s = s;
        ranges::sort(sorted_s);
        m[sorted_s].push_back(s);
    }

    // build ans
    vector<vector<string>> ans;
    ans.reserve(m.size());
    for (auto&#x26; [_, value] : m) {
        ans.push_back(value);
    }
    return ans;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>
<h2 id="最长连续序列评级6" class="group"><a aria-hidden="true" tabindex="-1" href="#最长连续序列评级6"><span class="icon icon-link"></span></a><a href="https://leetcode.cn/problems/longest-consecutive-sequence/description/?envType=study-plan-v2&#x26;envId=top-100-liked" rel="nofollow noopener noreferrer" target="_blank">最长连续序列</a>（评级6）</h2>
<p>给定一个未排序的整数数组 <code>nums</code> ，找出数字连续的最长序列（不要求序列元素在原数组中连续）的长度。</p>
<p>时间复杂度要求 <code>O(n)</code>.</p>
<details>
<summary>
思路
</summary>
<p>排序显然是一种解法。考虑基于散列的排序算法——比如基数排序——的话，其实应该也能做到 <code>O(n)</code>……</p>
<hr>
<p>再考虑无需排序的解法。</p>
<p>对每个元素 <code>x</code>，数以它为起点的序列最多能有多长。这需要不断检查下一个数是否存在，适合用 HashSet。</p>
<p>优化：在开始数之前先检查是否已存在 <code>x-1</code>，若存在则无需继续，因为从 <code>x-1</code> 开始肯定会更长。</p>
<p>时间复杂度<code>O(n)</code>. 虽然有二重循环，但每个元素最多遍历两次：外层循环一次，内层循环一次，所以只有<code>O(n)</code>.</p>
</details>
<details>
<summary>
代码
</summary>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="c++" data-theme="github-dark-dimmed"><code data-language="c++" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">int</span><span style="color:#DCBDFB"> longestConsecutive</span><span style="color:#ADBAC7">(</span><span style="color:#F69D50">vector</span><span style="color:#ADBAC7">&#x3C;</span><span style="color:#F47067">int</span><span style="color:#ADBAC7">></span><span style="color:#F47067">&#x26;</span><span style="color:#F69D50"> nums</span><span style="color:#ADBAC7">) {</span></span>
<span data-line=""><span style="color:#F47067">    int</span><span style="color:#ADBAC7"> n </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> nums.</span><span style="color:#DCBDFB">size</span><span style="color:#ADBAC7">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#ADBAC7">    unordered_set</span><span style="color:#F47067">&#x3C;int></span><span style="color:#DCBDFB"> s</span><span style="color:#ADBAC7">(nums.</span><span style="color:#DCBDFB">begin</span><span style="color:#ADBAC7">(), nums.</span><span style="color:#DCBDFB">end</span><span style="color:#ADBAC7">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    int</span><span style="color:#ADBAC7"> ans </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">    for</span><span style="color:#ADBAC7"> (</span><span style="color:#F47067">int</span><span style="color:#ADBAC7"> x : s) {</span></span>
<span data-line=""><span style="color:#768390">        // 剪枝</span></span>
<span data-line=""><span style="color:#F47067">        if</span><span style="color:#ADBAC7"> (s.</span><span style="color:#DCBDFB">contains</span><span style="color:#ADBAC7">(x </span><span style="color:#F47067">-</span><span style="color:#6CB6FF"> 1</span><span style="color:#ADBAC7">)) </span><span style="color:#F47067">continue</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">        int</span><span style="color:#ADBAC7"> y </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> x </span><span style="color:#F47067">+</span><span style="color:#6CB6FF"> 1</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#F47067">        while</span><span style="color:#ADBAC7"> (s.</span><span style="color:#DCBDFB">contains</span><span style="color:#ADBAC7">(y)) {</span></span>
<span data-line=""><span style="color:#F47067">            ++</span><span style="color:#ADBAC7">y;</span></span>
<span data-line=""><span style="color:#ADBAC7">        }</span></span>
<span data-line=""><span style="color:#768390">        // 所得连续序列：x..y</span></span>
<span data-line=""><span style="color:#ADBAC7">        ans </span><span style="color:#F47067">=</span><span style="color:#DCBDFB"> max</span><span style="color:#ADBAC7">(ans, y </span><span style="color:#F47067">-</span><span style="color:#ADBAC7"> x);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390">        // 小剪枝</span></span>
<span data-line=""><span style="color:#F47067">        if</span><span style="color:#ADBAC7"> (ans </span><span style="color:#F47067">*</span><span style="color:#6CB6FF"> 2</span><span style="color:#F47067"> >=</span><span style="color:#ADBAC7"> n) </span><span style="color:#F47067">break</span><span style="color:#ADBAC7">;</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#F47067">    return</span><span style="color:#ADBAC7"> ans;</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="int longestConsecutive(vector<int>&#x26; nums) {
    int n = nums.size();

    unordered_set<int> s(nums.begin(), nums.end());

    int ans = 0;
    for (int x : s) {
        // 剪枝
        if (s.contains(x - 1)) continue;

        int y = x + 1;
        while (s.contains(y)) {
            ++y;
        }
        // 所得连续序列：x..y
        ans = max(ans, y - x);

        // 小剪枝
        if (ans * 2 >= n) break;
    }
    return ans;
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
</details>]]></content>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-11-12T14:47:48.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[日常心算星期]]></title>
        <id>https://www.tinystar.me/post/1</id>
        <link href="https://www.tinystar.me/post/1"/>
        <updated>2025-10-09T07:32:01.000Z</updated>
        <summary type="html"><![CDATA[之前，在网上偶然看到一种 心算某一天是星期几 的实用算法。原文：心算某一天是星期几——康威裁决日算法 – 大老李聊数学 发明者是 数学家 J. H. 康威（John Horton Conway）——怎么又是你🤣。他喜欢“不务正业”，在趣味数学中贡献巨大，提出过生命游戏、超现实数、边看边说序列之类许多有趣又深刻的内容1。 康威发明的这个算法简单实用，只需少量记忆和计算，完美符合日常生活场景需求。我试过几次后就喜欢上了，到现在差不多已经用了一年多了。熟悉这个算法后，我日记里都彻底不写星期了。 我也经常反向使用这个算法根据星期推算日期（可能频率比用日期推星期还高些），因为我总是记不清今天是几号……...]]></summary>
        <content type="html"><![CDATA[<p>之前，在网上偶然看到一种 <strong>心算某一天是星期几</strong> 的实用算法。原文：<a href="https://dalaoliblog.wordpress.com/2021/05/22/%e5%bf%83%e7%ae%97%e6%9f%90%e4%b8%80%e5%a4%a9%e6%98%af%e6%98%9f%e6%9c%9f%e5%87%a0-%e5%ba%b7%e5%a8%81%e8%a3%81%e5%86%b3%e6%97%a5%e7%ae%97%e6%b3%95/" rel="nofollow noopener noreferrer" target="_blank">心算某一天是星期几——康威裁决日算法 – 大老李聊数学</a></p>
<p>发明者是 数学家 J. H. 康威（John Horton Conway）——怎么又是你🤣。他喜欢“不务正业”，在趣味数学中贡献巨大，提出过<a href="https://zh.wikipedia.org/wiki/%E5%BA%B7%E5%A8%81%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F" rel="nofollow noopener noreferrer" target="_blank">生命游戏</a>、<a href="https://zh.wikipedia.org/wiki/%E8%B6%85%E7%8F%BE%E5%AF%A6%E6%95%B8" rel="nofollow noopener noreferrer" target="_blank">超现实数</a>、<a href="https://eigolomoh.bitbucket.io/math/look_and_say.1.html" rel="nofollow noopener noreferrer" target="_blank">边看边说序列</a>之类许多有趣又深刻的内容<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>。</p>
<p>康威发明的这个算法简单实用，只需少量记忆和计算，完美符合日常生活场景需求。我试过几次后就喜欢上了，到现在差不多已经用了一年多了。熟悉这个算法后，我日记里都彻底不写星期了。</p>
<p>我也经常反向使用这个算法根据星期推算日期（可能频率比用日期推星期还高些），因为我总是记不清今天是几号……</p>
<p>由于真的在用，所以也有做些调整。本文会基于我的实践经验，重新梳理康威提出的这个心算星期几的算法，使之能够轻松用于日常生活。并补充了一点数学推导来解释这个神奇的算法为什么是对的。</p>
<blockquote>
<p>因此有些地方与大老李的文章稍有不同，可能不做标注。</p>
</blockquote>
<p>注：以下经常使用「对7取模」。因为1星期=7天，算出“星期八”甚至“星期十五”时应当能够正确地对7取模得到「星期一」。</p>
<h2 id="核心原理" class="group"><a aria-hidden="true" tabindex="-1" href="#核心原理"><span class="icon icon-link"></span></a>核心原理</h2>
<p>如果我们已经知道了本月某一天的星期，那么计算其他天的星期就很容易了。比如，已知 7月11日 是周五，那么就能算出 9日是周三、14日是周一，以此类推。</p>
<p>那么，我们只要为1年12月的每个月都背下某一天的星期，不就可以心算每一天的星期了吗？但是这样要背12个日期的星期，未免记忆量过大，还容易出错。</p>
<p>康威算法的精髓就在于，它在每个月中挑选出特殊的一天作为基准点，称作“裁决日”(Doomsday)，这些裁决日具有相同的星期，又容易记忆。</p>
<p>2月是一年中唯一天数会变化的月份（平年28天，闰年29天）。因此，康威将「2月的最后一天」作为裁决日。这很自然，而且还可以自动处理闰年问题。</p>
<p>那么其他月呢？我们要求它们的裁决日与2月的最后一天具有相同的星期，然后尽量好记。</p>
<p>1月、2月、3月特殊处理；4月到12月：</p>
<ul>
<li><strong>偶数月</strong>：4月4日、6月6日、8月8日、10月10日、12月12日。<strong>日期=月数</strong>，很好记。</li>
<li><strong>奇数月</strong>：5月9日，9月5日，7月11日，11月7日。<strong>朝九晚五</strong>、<strong>7-11便利店</strong>。</li>
</ul>
<p>1月、2月、3月：</p>
<ul>
<li><strong>2月的最后1天</strong>，2月28/29日。或者减去4周，得到 <strong>0/1</strong>日。我更喜欢后者，更方便计算。</li>
<li>3月是3月0日。不难注意到，“3月0日”=2月的最后一天。</li>
<li>1月的3/4日，取决于是否是闰年。1314 谐音“<strong>一生一世</strong>”。</li>
</ul>
<p>只要算一算裁决日之间间隔的天数，就能知道它们为什么具有相同的星期——日期之差恰好是7的倍数。比如，偶数月的裁决日日期总要+2的原因是<code>30+31 ≡ 5 ≡ -2 (mod 7)</code>.</p>
<h2 id="高度优化的算法" class="group"><a aria-hidden="true" tabindex="-1" href="#高度优化的算法"><span class="icon icon-link"></span></a>高度优化的算法</h2>
<p>不知道如何用自然语言简明阐述，直接上伪代码吧。</p>
<p>按顺序提供几个函数：</p>
<ol>
<li>日期和星期之间的偏移量（星期减日期）已知。反向使用就是 星期=>日期。</li>
<li>同月份的某日的星期已知。</li>
<li>同年份的裁决日的星期已知。</li>
</ol>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="rust" data-theme="github-dark-dimmed"><code data-language="rust" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">fn</span><span style="color:#DCBDFB"> day_to_weekday</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">&#x26;</span><span style="color:#6CB6FF">self</span><span style="color:#ADBAC7">, day) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    (day </span><span style="color:#F47067">+</span><span style="color:#6CB6FF"> self</span><span style="color:#F47067">.</span><span style="color:#ADBAC7">offset) </span><span style="color:#F47067">%</span><span style="color:#6CB6FF"> 7</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">fn</span><span style="color:#DCBDFB"> weekday_to_day</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">&#x26;</span><span style="color:#6CB6FF">self</span><span style="color:#ADBAC7">, weekday) {</span></span>
<span data-line=""><span style="color:#ADBAC7">    (day </span><span style="color:#F47067">-</span><span style="color:#6CB6FF"> self</span><span style="color:#F47067">.</span><span style="color:#ADBAC7">offset) </span><span style="color:#F47067">%</span><span style="color:#6CB6FF"> 7</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">fn</span><span style="color:#DCBDFB"> set_offset</span><span style="color:#ADBAC7">(</span><span style="color:#F47067">&#x26;mut</span><span style="color:#6CB6FF"> self</span><span style="color:#ADBAC7">, doomsday) {</span></span>
<span data-line=""><span style="color:#6CB6FF">    self</span><span style="color:#F47067">.</span><span style="color:#ADBAC7">offset </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> (</span><span style="color:#6CB6FF">self</span><span style="color:#F47067">.</span><span style="color:#ADBAC7">doomsweekday </span><span style="color:#F47067">-</span><span style="color:#ADBAC7"> doomsday) </span><span style="color:#F47067">%</span><span style="color:#6CB6FF"> 7</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">fn</span><span style="color:#DCBDFB"> doomsday</span><span style="color:#ADBAC7">(month) </span><span style="color:#F47067">-></span><span style="color:#ADBAC7"> {</span></span>
<span data-line=""><span style="color:#F47067">    match</span><span style="color:#ADBAC7"> month {</span></span>
<span data-line=""><span style="color:#6CB6FF">        1</span><span style="color:#F47067"> =></span><span style="color:#F47067"> if</span><span style="color:#DCBDFB"> is_leap_year</span><span style="color:#ADBAC7">() { </span><span style="color:#6CB6FF">4</span><span style="color:#ADBAC7"> } </span><span style="color:#F47067">else</span><span style="color:#ADBAC7"> { </span><span style="color:#6CB6FF">3</span><span style="color:#ADBAC7"> }, </span><span style="color:#768390">// 一生一世</span></span>
<span data-line=""><span style="color:#6CB6FF">        2</span><span style="color:#F47067"> =></span><span style="color:#F47067"> if</span><span style="color:#DCBDFB"> is_leap_year</span><span style="color:#ADBAC7">() { </span><span style="color:#6CB6FF">1</span><span style="color:#ADBAC7"> } </span><span style="color:#F47067">else</span><span style="color:#ADBAC7"> { </span><span style="color:#6CB6FF">0</span><span style="color:#ADBAC7"> }, </span><span style="color:#768390">// 或 if is_leap_year() { 29 } else { 28 } </span></span>
<span data-line=""><span style="color:#6CB6FF">        3</span><span style="color:#F47067"> =></span><span style="color:#6CB6FF"> 0</span><span style="color:#ADBAC7">, </span><span style="color:#768390">// 3月0日 = 2月的最后一天</span></span>
<span data-line=""><span style="color:#6CB6FF">        4</span><span style="color:#F47067"> |</span><span style="color:#6CB6FF"> 6</span><span style="color:#F47067"> |</span><span style="color:#6CB6FF"> 8</span><span style="color:#F47067"> |</span><span style="color:#6CB6FF"> 10</span><span style="color:#F47067"> |</span><span style="color:#6CB6FF"> 12</span><span style="color:#F47067"> =></span><span style="color:#ADBAC7"> month,</span></span>
<span data-line=""><span style="color:#6CB6FF">        5</span><span style="color:#F47067"> =></span><span style="color:#6CB6FF"> 9</span><span style="color:#ADBAC7">, </span><span style="color:#768390">// 朝九</span></span>
<span data-line=""><span style="color:#6CB6FF">        9</span><span style="color:#F47067"> =></span><span style="color:#6CB6FF"> 5</span><span style="color:#ADBAC7">, </span><span style="color:#768390">// 晚五</span></span>
<span data-line=""><span style="color:#6CB6FF">        7</span><span style="color:#F47067"> =></span><span style="color:#6CB6FF"> 11</span><span style="color:#ADBAC7">, </span><span style="color:#768390">// 7-11</span></span>
<span data-line=""><span style="color:#6CB6FF">        11</span><span style="color:#F47067"> =></span><span style="color:#6CB6FF"> 7</span><span style="color:#ADBAC7">, </span><span style="color:#768390">// 7-11</span></span>
<span data-line=""><span style="color:#ADBAC7">        _ </span><span style="color:#F47067">=></span><span style="color:#DCBDFB"> unreachable!</span><span style="color:#ADBAC7">(</span><span style="color:#96D0FF">"Month must be between 1 and 12"</span><span style="color:#ADBAC7">)</span></span>
<span data-line=""><span style="color:#ADBAC7">    }</span></span>
<span data-line=""><span style="color:#ADBAC7">}</span></span><button type="button" title="Copy code" aria-label="Copy code" data="fn day_to_weekday(&#x26;self, day) {
    (day + self.offset) % 7
}

fn weekday_to_day(&#x26;self, weekday) {
    (day - self.offset) % 7
}

fn set_offset(&#x26;mut self, doomsday) {
    self.offset = (self.doomsweekday - doomsday) % 7
}

fn doomsday(month) -> {
    match month {
        1 => if is_leap_year() { 4 } else { 3 }, // 一生一世
        2 => if is_leap_year() { 1 } else { 0 }, // 或 if is_leap_year() { 29 } else { 28 } 
        3 => 0, // 3月0日 = 2月的最后一天
        4 | 6 | 8 | 10 | 12 => month,
        5 => 9, // 朝九
        9 => 5, // 晚五
        7 => 11, // 7-11
        11 => 7, // 7-11
        _ => unreachable!(&#x22;Month must be between 1 and 12&#x22;)
    }
}" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>根据实践经验优化了方案：月内专门缓存一个“偏移量”，而不总是从裁决日开始推。这样可以减少计算步骤。</p>
<p>比如，2025年7月的裁决日11日是周五，那么偏移量是 <code>5-11 ≡ -6 ≡ +1 (mod 7)</code>，那么我在这个月里只需要记住偏移量 <code>+1</code>就可以了。这样，下次计算时就可以将2次加减法减少为1次。比如计算25日的星期：<code>25+1=26</code>，模7得<code>5</code>，所以25日是星期五，轻松心算。</p>
<p>例题：已知2025年的裁决日是星期五。请问2025年的9月30日是星期几？</p>
<ol>
<li>“朝九晚五”，9月的裁决日是5日。</li>
<li>那么5日是星期五，偏移量5-5=<code>0</code>.</li>
<li>30日的星期是<code>(30 + 0) % 7 = 2</code>.</li>
</ol>
<p>2025年的9月30日是星期二<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup>。</p>
<h2 id="计算某年的裁决日星期" class="group"><a aria-hidden="true" tabindex="-1" href="#计算某年的裁决日星期"><span class="icon icon-link"></span></a>计算某年的裁决日星期</h2>
<p>要心算其他年份的某个日期的星期怎么办？把每一年的裁决日星期都背下来，也太难了，难免会记不清。</p>
<p>康威当然也研究了这类情况。我们可以记下某个“基础年份”的裁决日星期，然后计算目标年份与基础年份的“偏移量”，就可以算出目标年份的裁决日星期了。基础年份通常取「世纪首年」。</p>
<blockquote>
<p>为什么取世纪首年？因为闰年在100的倍数又有特判（闰年的定义：能被4整除但不能被100整除的年份，或者能被400整除的年份），考虑进来的话就太麻烦了，限制在同一世纪内可以避开这条规则🤣。</p>
</blockquote>
<p>具体来说，其实我们只会用到2个世纪：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="sh" data-theme="github-dark-dimmed"><code data-language="sh" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F69D50">$</span><span style="color:#96D0FF"> date</span><span style="color:#6CB6FF"> -d</span><span style="color:#96D0FF"> 2000-02-29</span><span style="color:#96D0FF"> +%A</span></span>
<span data-line=""><span style="color:#F69D50">星期二</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F69D50">$</span><span style="color:#96D0FF"> date</span><span style="color:#6CB6FF"> -d</span><span style="color:#96D0FF"> 1900-02-28</span><span style="color:#96D0FF"> +%A</span><span style="color:#768390"> # 这个我一次都没用到过</span></span>
<span data-line=""><span style="color:#F69D50">星期三</span></span><button type="button" title="Copy code" aria-label="Copy code" data="$ date -d 2000-02-29 +%A
星期二

$ date -d 1900-02-28 +%A # 这个我一次都没用到过
星期三" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>那么接下来把 <strong>目标年份相对于基础年份的偏移量</strong> 算出来就好了。康威对此也给出了一个漂亮的算法，我用自己的方式重新陈述如下，绝对好记又好算。</p>
<p>计算给定年份的裁决日星期：</p>
<ol>
<li>取两位年份；</li>
<li>浮现一个对12的带余除法，维护商和余数；</li>
<li>把注意力集中在余数，算出除以4的商放在下面；</li>
<li>商下面的空位正好放置基准年的裁决日星期；</li>
<li>把脑海中的这4个数模7求和。</li>
</ol>
<p>12 和 4 这两个数都非常容易联想：一年共有12个月，闰年是每4年一次。太完美了！</p>
<blockquote>
<p>虽然后面推导时会发现这里的12跟一年12月没有任何关系，但不影响我们这样记忆。</p>
</blockquote>
<p>例题：2026年1月1日是星期几？</p>
<p>首先计算 2026年 的裁决日星期。</p>
<ol>
<li>取两位年份：<code>26</code></li>
<li>浮现一个对12的带余除法，维护商和余数：<code>26 / 12 = 2 ... 2</code></li>
</ol>
<table>
<thead>
<tr>
<th align="left"><strong>2</strong></th>
<th align="left"><strong>2</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">?</td>
<td align="left">?</td>
</tr>
</tbody>
</table>
<ol start="3">
<li>把注意力集中在余数，算出除以4的商放在下面：<code>2 // 4 = 0</code></li>
</ol>
<table>
<thead>
<tr>
<th align="left"><strong>2</strong></th>
<th align="left"><strong>2</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">?</td>
<td align="left"><strong>0</strong></td>
</tr>
</tbody>
</table>
<ol start="4">
<li>商下面的空位正好放置基准年的裁决日星期：2000年的裁决日是星期<code>2</code>。</li>
</ol>
<table>
<thead>
<tr>
<th align="left"><strong>2</strong></th>
<th align="left"><strong>2</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><strong>2</strong></td>
<td align="left"><strong>0</strong></td>
</tr>
</tbody>
</table>
<ol start="5">
<li>把脑海中的这4个数模7求和：<code>6</code>.</li>
</ol>
<p>2026年的裁决日是星期六。</p>
<p>这年是平年，1月的裁决日是<code>3</code>日。偏移量是<code>6-3=3</code>。那么1日的星期就是<code>1+3=4</code>.</p>
<p>2026年1月1日是星期四<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup>.</p>
<p>大老李还介绍了另一个“奇+11”算法；但那个算法光是「包含分支」就让我避而远之了，不值一提。</p>
<h3 id="算法推导" class="group"><a aria-hidden="true" tabindex="-1" href="#算法推导"><span class="icon icon-link"></span></a>算法推导</h3>
<p>上面这个算法看起来很难相信是正确的：又商又余数又再除一次再全加起来，这就对了？12是怎么回事？</p>
<p>此外，我们也想知道，会不会有更方便的算法？</p>
<p>所以来推导一下吧。</p>
<p>基本原理：<code>365 % 7 =1</code>，<code>366 % 7 = 2</code>。裁决日星期每年+1，闰年再额外多+1。</p>
<p>则有递推公式：</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="rust" data-theme="github-dark-dimmed"><code data-language="rust" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#ADBAC7">doomsweekday </span><span style="color:#F47067">+=</span><span style="color:#6CB6FF"> 1</span><span style="color:#F47067"> +</span><span style="color:#DCBDFB"> is_leap_year</span><span style="color:#ADBAC7">()</span></span><button type="button" title="Copy code" aria-label="Copy code" data="doomsweekday += 1 + is_leap_year()" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>——其实，这个递推公式本身就很实用了：每年根据「今年是不是闰年？」，在去年的基础上更新一下即可。</p>
<p>而由于经常用到，当年的裁决日星期很自然地就会记下来，然后就无需重新计算了。比如2025年的裁决日星期是周五，敲这句话我不假思索。读者不难验证：2024年的裁决日星期是周四，2026年是周六、2027年是周日，而2028年是周二。</p>
<p>不过，要算其他年份的话还是不方便，还是需要通项公式。继续推导吧。</p>
<p>根据这个递推公式，可以立刻得到计算偏移量的最直接的算法：<code>yy + yy // 4</code>. 其中yy指两位年份。</p>
<p>但这显然不便于心算！</p>
<p>平年+1，闰年+2，这样 <strong>连续4年则模7余5</strong>。我们当然想凑个简单的，比如余0或余1。</p>
<p>显然连续28年则余0。逐个计算过去还能发现一个很不错的：连续12年则余1。</p>
<p>以28年为一组，则得 <code>let remainder = yy % 28; remainder + remainder // 4</code>。</p>
<p>以12年为一组，则得 <code>let (quotient, remainder) = divmod(yy, 12); quotient + remainder + remainder // 4</code>。</p>
<p>实践表明，以12年为一组，中间数值都很小，最方便心算。并且12恰巧能与「1年12月」对应上，非常好记。这正是康威原版算法。</p>
<h2 id="更一般的情况" class="group"><a aria-hidden="true" tabindex="-1" href="#更一般的情况"><span class="icon icon-link"></span></a>更一般的情况？</h2>
<p>更一般的情况的话，要应对闰年在100和400倍数时的特殊处理，麻烦很多，算了吧。反正这已经不是日常了，可以从容动用工具。</p>
<p>例：</p>
<p><img src="https://github.com/user-attachments/assets/aa01db89-3255-4ce4-bc06-655d16facf97" alt="9810年1月19日是星期几"></p>
<section data-footnotes class="footnotes"><h2 class="sr-only group" id="footnote-label"><a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>曾经跟同学课间闲聊，给同学演示边看边说序列，但是把定义记错了，记成了数“总数”，给同学演示时发现不对劲。这形成了全新的变种“计数序列”。高二一年+高三上，投入许多时间研究了计数序列，并把主要成果整理成一篇论文，水了个地区赛事奖项。<br>
康威在2020年因新冠疫情逝世。我当时刚升入大学，偶然从某篇文章的注释中得知这个不幸的消息，蓦然有种连接又断开的感觉。<br>
永远怀念。 <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>如果你愿意在这天给我发一句「生日快乐！」，我会很开心。 <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>提前祝新年快乐！ <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
        <category label="把多余的事情也探索了吧"/>
        <category label="理念技术"/>
        <published>2025-05-12T14:40:56.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[调试之道]]></title>
        <id>https://www.tinystar.me/post/6</id>
        <link href="https://www.tinystar.me/post/6"/>
        <updated>2025-09-30T05:08:10.000Z</updated>
        <summary type="html"><![CDATA[「调试」也算是颇为通用的技能了，并不局限于调试程序——而是所有「足够确定的系统」。而程序本身恰好就是一个完全确定的系统，这也正是编程的一大魅力。 强烈推荐书籍 《调试九法》。如同书中所言，这本书教你如何“把调试艺术转化为科学”。 本书很薄（150页），内容也不复杂，又穿插了很多案例故事，很好读。而难点在于，真的遇到问题时，要能冷静下来应用这些方法，而不是陷入慌乱、依赖直觉、胡乱折腾碰运气。事实上，后者很可能更费时，甚至还一事无成。 我第一次读时记了整整一屏笔记，后来全删了，只保留了全书强调的九条调试规则（现在已经背下来了）。 后来实习过程中，我发现，相比学校大作业和科协里的项目，实际开发中纯粹...]]></summary>
        <content type="html"><![CDATA[<p>「调试」也算是颇为通用的技能了，并不局限于调试程序——而是所有「<strong>足够确定的系统</strong>」。而程序本身恰好就是一个完全确定的系统，这也正是编程的一大魅力。</p>
<br>
<p>强烈推荐书籍 <strong>《调试九法》</strong>。如同书中所言，这本书教你如何“把调试艺术转化为科学”。</p>
<p>本书很薄（150页），内容也不复杂，又穿插了很多案例故事，很好读。而难点在于，真的遇到问题时，要能冷静下来应用这些方法，而不是陷入慌乱、依赖直觉、胡乱折腾碰运气。事实上，后者很可能更费时，甚至还一事无成。</p>
<p>我第一次读时记了整整一屏笔记，后来全删了，只保留了全书强调的九条调试规则（现在已经背下来了）。</p>
<p>后来实习过程中，我发现，相比学校大作业和科协里的项目，实际开发中纯粹写新代码的时间反而少了，更多是在完成 benchmark、debug、profiling 相关的工作。而支撑我应对这些任务的调试功底，很大程度上就来自于这本一两年前偶然读到的《调试九法》。这确实是本非常好的书。</p>
<h2 id="抄录九条调试规则" class="group"><a aria-hidden="true" tabindex="-1" href="#抄录九条调试规则"><span class="icon icon-link"></span></a>抄录九条调试规则</h2>
<div class="markdown-alert markdown-alert-important"><p class="markdown-alert-title"><svg class="octicon octicon-report mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Important</p>
<ul>
<li><strong>理解系统</strong>；</li>
<li><strong>制造失败</strong>；</li>
<li><strong>不要想，而要看</strong>；</li>
<li><strong>分而治之</strong>；</li>
<li><strong>一次只改一个地方</strong>；</li>
<li><strong>保持审计跟踪</strong>；</li>
<li><strong>检查插头</strong>；</li>
<li><strong>获得全新观点</strong>；</li>
<li><strong>没有修复的 bug 仍将存在</strong>。</li>
</ul>
</div>
<p>这九条调试规则值得背下来，一则利于自我提醒按图索骥，二则利于与他人交流。</p>
<h2 id="抄录每条规则的章末小结" class="group"><a aria-hidden="true" tabindex="-1" href="#抄录每条规则的章末小结"><span class="icon icon-link"></span></a>抄录每条规则的章末小结</h2>
<p>这些小结与文中讲述的案例故事相照应，是对具体调试规则的深入阐释。本书的精华所在。</p>
<p>宜根据自己的情况针对性地防范部分思维漏洞。比如，对之前的我来说，需注意：添加插装、改用易于识别的输入等之后，应当在继续调试前再次“制造失败”，确保所做的改变没有隐藏bug。</p>
<p>直到这些道理已经完全内化，从心所欲而不逾矩。</p>
<h3 id="理解系统" class="group"><a aria-hidden="true" tabindex="-1" href="#理解系统"><span class="icon icon-link"></span></a>理解系统</h3>
<p>这是第一条规则，因为它是最重要的。</p>
<ul>
<li><strong>阅读手册</strong>。它会告诉你在使用除草机时，要在除草头上涂润滑油，这样除草绳就不会被烧化。</li>
<li><strong>仔细阅读每个细节</strong>。有关微处理器如何处理中断的详细信息就隐藏在数据手册的第37页。</li>
<li><strong>掌握基础知识</strong>。电锯本来就会发出很大的噪声。</li>
<li><strong>了解工作流程</strong>。引擎的转速可能与轮胎的转速不同，这是由传动轴造成的。</li>
<li><strong>了解工具</strong>。弄清楚体温计的哪一端才是用来测量体温的，弄清楚Glitch-O-Matic逻辑分析器的强大功能是如何使用的。</li>
<li><strong>查阅细节</strong>。连爱因斯坦都会去查阅细节，而Kneejerk却盲目相信自己的记忆力。</li>
</ul>
<h3 id="制造失败" class="group"><a aria-hidden="true" tabindex="-1" href="#制造失败"><span class="icon icon-link"></span></a>制造失败</h3>
<p>虽然看起来很简单，但如果不制造失败的话，调试就会变得很困难。</p>
<ul>
<li><strong>制造失败</strong>。目的是为了观察它，找到原因，并检查是否已修复。</li>
<li><strong>从头开始</strong>。修车工需要知道汽车车窗在被冻结之前你洗过车。</li>
<li><strong>引发失败</strong>。用喷水管向漏雨的那扇窗子喷水。</li>
<li><strong>但不要模拟失败</strong>。用喷水管向漏雨的那扇窗子喷水，而不要向另一扇不同的、“类似的”窗子喷水。</li>
<li><strong>查找不受你控制的条件（正是它导致了间歇性失败）</strong>。改变能够改变的每件事情，振动、摇晃、扭曲，直到再现失败。</li>
<li><strong>记录每件事情，并找到间歇性bug的特征</strong>。我们的绑定系统总是只在呼叫顺序错乱时才会失败。</li>
<li><strong>不要过于相信统计数据</strong>。绑定问题看起来与时间段有关，但实际上真正的原因是当地的年轻人占用了电话线路。</li>
<li><strong>要认识到“那”是可能会发生的</strong>。甚至冰淇淋的口味也会影响汽车的发动。</li>
<li><strong>永远不要丢掉一个调试工具</strong>。自动击球板可能在某一天就会派上用场。</li>
</ul>
<h3 id="不要想而要看" class="group"><a aria-hidden="true" tabindex="-1" href="#不要想而要看"><span class="icon icon-link"></span></a>不要想，而要看</h3>
<p>凭空想象，问题可能有几千条原因。而实际的原因只有去看了才能发现。</p>
<ul>
<li><strong>观察失败</strong>。高级工程师看到了真实的问题，并且能够找到原因。而初级工程师们认为他们知道错误发生在哪里，结果他们修复的地方根本没有出错。</li>
<li><strong>查看细节</strong>。听到水泵似乎发出声音时不要停下来。到地下室查明是哪个水泵。</li>
<li><strong>植入插装工具</strong>。使用源代码调试器、调试日志、状态消息、信号灯和臭鸡蛋的气味。</li>
<li><strong>添加外部插装工具</strong>。使用分析器、示波器、量表、金属检测仪、心电图仪和肥皂泡。</li>
<li><strong>不要害怕深入研究</strong>。虽然它是软件成品，但它出问题了，你必须打开并修复它。</li>
<li><strong>注意海森堡效应</strong>。不要让仪器影响了系统。</li>
<li><strong>猜测只是为了确定搜索的重点</strong>。大胆地猜测内存时序发生了错误，但在修复之前应该先查看它。</li>
</ul>
<h3 id="分而治之" class="group"><a aria-hidden="true" tabindex="-1" href="#分而治之"><span class="icon icon-link"></span></a>分而治之</h3>
<p>当bug的藏身之地不断被缩小一半时，它将很难再隐藏下去。</p>
<ul>
<li><strong>通过逐次逼近缩小搜索范围</strong>。猜测1~100内的一个数字，只需7次。</li>
<li><strong>确定范围</strong>。如果数字是135而你却认为它在1~100内，那么你必须扩大范围。</li>
<li><strong>确定你位于bug的哪一侧</strong>。如果你所在的位置有排放物，则排放管就在上游。如果没有排放物，则排放管就在下游。</li>
<li><strong>使用易于查看的测试模式</strong>。从干净、清澈的水开始，以便当排放物进入河流中时很容易看到它。</li>
<li><strong>从有问题的一端开始搜索</strong>。如果你验证的是正确的部分，那么需要验证的地方太多了。应该从有问题的地方开始，然后向后追查原因。</li>
<li><strong>修复已知bug。bug互相保护，互相隐藏</strong>。因此一旦找到，立即修复它们。</li>
<li><strong>首先消除噪声干扰</strong>。注意那些导致系统问题的干扰因素。但对一些无足轻重的问题不要过于极端，也不要为了追求完美而去修改所有地方。</li>
</ul>
<h3 id="一次只改一个地方" class="group"><a aria-hidden="true" tabindex="-1" href="#一次只改一个地方"><span class="icon icon-link"></span></a>一次只改一个地方</h3>
<p>我们在生活中要有一点先见之明。如果你所做的更改没有起到预期的作用，那么就把它改回来。它们可能会产生无法预料的影响。</p>
<ul>
<li><strong>隔离关键因素</strong>。如果你在检查日照时间的影响，就不要改变灌溉方案。</li>
<li><strong>用双手抓住黄铜杆</strong>。如果你在不知道具体发生了什么问题的情况下就试图去修理核潜艇，可能会引发一次水下的切尔诺贝利爆炸。</li>
<li><strong>一次只改一个测试</strong>。我之所以知道我的VGA采集相位被破坏了，就是因为其他东西都没有发生改变。</li>
<li><strong>与正常情况进行比较</strong>。如果所有出错的情况都有一些特征，而这些特征是正常情况所没有的，那么你就找到了问题所在。</li>
<li><strong>确定自从上一次正常工作以来你改变了什么地方</strong>。我的工友改变了唱机转盘上的唱头，因此这是一个很好的调试起点。</li>
</ul>
<h3 id="保持审计跟踪" class="group"><a aria-hidden="true" tabindex="-1" href="#保持审计跟踪"><span class="icon icon-link"></span></a>保持审计跟踪</h3>
<p>不要只是在心里记住“保持审计跟踪”这条规则，而要把它写下来。</p>
<ul>
<li><strong>把你的操作、操作的顺序和结果全部记录下来</strong>。你上一次喝咖啡是什么时候？你的头痛是从什么时候开始的？</li>
<li><strong>要知道，任何细节都可能是重要的</strong>。视频压缩芯片的崩溃是由于格子衬衫造成的。</li>
<li><strong>把事件关联到一起</strong>。“它发出噪声，从21:04:53开始，持续4秒”比仅仅说“它发出噪声”要好得多。</li>
<li><strong>用于设计的审计跟踪在测试中也非常有用</strong>。软件配置控制工具可以告诉你哪次修订引入了bug。</li>
<li><strong>把事情记录下来</strong>！无论那个时刻多么恐怖，都要把它记到备忘录中，这样你才不会忘记。</li>
</ul>
<h3 id="检查插头" class="group"><a aria-hidden="true" tabindex="-1" href="#检查插头"><span class="icon icon-link"></span></a>检查插头</h3>
<p>一些显而易见的假设往往是错误的。请恕我赘述，假设错误通常是最容易修复的错误。</p>
<ul>
<li><strong>置疑你的假设</strong>。是否运行了正确的代码？是不是燃气用完了？插头是否已插好？</li>
<li><strong>从头开始</strong>。是否正确地对内存进行了初始化？是否按了除草机上的“primer bulb”按钮？开关是否已打开？</li>
<li><strong>对工具进行测试</strong>。是否运行了正确的编译器？燃料油表是否被粘住了？量表是不是没电了？</li>
</ul>
<h3 id="获得全新观点" class="group"><a aria-hidden="true" tabindex="-1" href="#获得全新观点"><span class="icon icon-link"></span></a>获得全新观点</h3>
<p>不管怎样，你都需要休息一下，喝杯咖啡。</p>
<ul>
<li><strong>征求别人的意见</strong>。甚至一个不说话的人体模特也能帮助你认识到你先前没有注意到的事情。</li>
<li><strong>获取专业知识</strong>。只有VGA视频采集卡的厂商才能够肯定相位功能发生了错误。</li>
<li><strong>听取别人的经验</strong>。别人会告诉你车内顶灯的线被挤压出来了。</li>
<li><strong>帮助无处不在</strong>。同事、供应商、网络，还有书店，都在等待着为你提供帮助。</li>
<li><strong>放下面子</strong>。bug发生了。以除掉bug为自豪，而不要非得以自己除掉bug才为自豪。</li>
<li><strong>报告症状，而不要讲你的理论</strong>。不要把别人拖进你的思维定式中。</li>
<li><strong>你提出的问题不必十分肯定</strong>。甚至连“穿了格子衬衫”这样的事情也可以提出来。</li>
</ul>
<h3 id="没有修复的-bug-仍将存在" class="group"><a aria-hidden="true" tabindex="-1" href="#没有修复的-bug-仍将存在"><span class="icon icon-link"></span></a>没有修复的 bug 仍将存在</h3>
<p>现在你已经掌握了所有的技术，没有理由再让bug存在了。</p>
<ul>
<li><strong>查证问题确实已被修复</strong>。不要假设是电路的问题，而仍然让汽车带着脏的滤油嘴上路。</li>
<li><strong>查证确实是你的修复措施解决了问题</strong>。口中大喊“Wubba!”并不是使计算机打开的窍门。</li>
<li><strong>要知道，bug从来不会自己消失</strong>。使用最初导致它失败的方法再次制造失败。如果必须交付产品，那么就在产品中设计一个用于捕捉bug的“陷阱”，以便产品在客户现场发生失败时，把它捉住。</li>
<li><strong>从根本上解决问题</strong>。在烧坏另一台变压器之前，先把无用的8音轨磁带卡座扔掉。</li>
<li><strong>对过程进行修复</strong>。不要只是擦掉地上的油，而要纠正设计机器的方式。</li>
</ul>
<h2 id="抄录第12章通过一个案例讲述所有规则" class="group"><a aria-hidden="true" tabindex="-1" href="#抄录第12章通过一个案例讲述所有规则"><span class="icon icon-link"></span></a>抄录第12章：《通过一个案例讲述所有规则》</h2>
<p>首先再次回顾九条调试规则：</p>
<div class="markdown-alert markdown-alert-important"><p class="markdown-alert-title"><svg class="octicon octicon-report mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Important</p>
<ul>
<li><strong>理解系统</strong>；</li>
<li><strong>制造失败</strong>；</li>
<li><strong>不要想，而要看</strong>；</li>
<li><strong>分而治之</strong>；</li>
<li><strong>一次只改一个地方</strong>；</li>
<li><strong>保持审计跟踪</strong>；</li>
<li><strong>检查插头</strong>；</li>
<li><strong>获得全新观点</strong>；</li>
<li><strong>没有修复的 bug 仍将存在</strong>。</li>
</ul>
</div>
<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg class="octicon octicon-info mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p><strong>案例故事</strong></p>
<p>公司T有一台小设备，它有时会拒绝启动，因为有个微处理器无法正确读取使用备用电池供电的(battery-backed-up)内存。有些内存单元的情况比其他单元更糟，如果一个单元某次启动时失败，下次就无法再工作。这意味着内存中的数据没问题——问题在于读取它。</p>
<p>工程师A对此问题进行了研究，得出的结论是问题是由于某种数据总线噪声引起的，导致无法正确读取内存。系统是一个包含内存和一个使用备用电池供电的内存控制器的小型电路板，它被添加到一块现有的主板上。该电路板与主板之间的连接器只有两个接地引脚和一个5伏引脚，因此为了降低噪声，工程师在两块板之间增加了一根很粗的接地线。他还增加了一个电容器，作为小板上的蓄电池。他还写了一张工程变更清单，并获得批准，然后将改动应用于制造过程。</p>
<p>当第一批经过改动的电路板生产出来时，工程师B接到了电话通知，因为在这批主板中,很多使用效果与以前完全一样。</p>
<p>工程师B早上9点开始工作，他把示波器接到数据线上，然后观察当主板试图访问内存时会出现什么情况。当系统出现故障时，噪声并未给数据造成多大损坏，但数据都是1。查看读取脉冲的情况时，他惊讶地发现根本没有读取脉冲。现在，这是一个严重的信号丢失问题，而不仅仅是噪声。主板上的微处理器正在进行读取，并发送一个读取脉冲给内存控制器芯片，但是读取脉冲并未从内存控制器芯片中出来并到达内存（参见图12-1）。</p>
<p>工程师B快速查阅了手册，发现内存控制器芯片的作用是，当电源电力不足时防止访问内存（因此也屏蔽了读取脉冲）。这看起来很有道理，但当时电源似乎没有出现明显的问题。</p>
<p>上午9:45，工程师打电话给芯片制造商，与一名应用工程师进行了沟通。该工程师说：“哦，你可能在5伏电源和芯片电源之间安放了一个二极管。如果你这么做，当供给5伏的电压时，芯片就会认为电力不足，从而锁定。”毫无疑问，他们的设计与这名应用工程师描述的完全一样。（因为后面给电路板增加了元件，同时未对主板进行任何改动,这样就修改了芯片制造商推荐的设计，在当时看来这是一种非常合理的方式。）</p>
<p>按照应用工程师建议，工程师B需要将主板的另一条线连上，才能获得原始的5伏电压。当他这样做的时候，系统就工作正常了。他将修复还原，目睹它发生故障，然后再次进行同样的修复，接着进行测试。一切正常。10:15，工程师B圆满完成了所有工作。（在这次修复过程中，编写工程变更清单所花的时间确实比调试过程要长。）</p>
</div>
<p>让我们总结一下这个案例中的规则。</p>
<p><strong>理解系统</strong>。工程师A从头至尾都没有看数据手册。工程师B看过了数据手册，而且当他在其中没找到读取脉冲消失的原因时，他知道芯片有可能是“嫌疑犯”，因此心里很清楚应该联系哪家厂商。他也马上知道，没有读取脉冲会导致数据全部为1。</p>
<p><strong>制造失败</strong>。系统在某种程度上有规律地出现故障的事实，会使工程师B的工作变得轻松。（同时会让工程师A的处境变得尴尬。）工程师B看到了数据都为1和读取脉冲丢失。</p>
<p><strong>不要想，而要看</strong>。工程师A从未看到数据全部为1，也没有看到读取脉冲丢失，因此不可能很快知道这不是一个噪声问题。</p>
<p><strong>分而治之</strong>。工程师B查看了接口，发现了错误数据。他接着查看内存读取脉冲，发现它丢失了，因此他顺藤摸瓜，发现了微处理器脉冲没有正确地到达电路板。最后找出了正常读取脉冲与丢失的读取脉冲之间的出错的芯片。</p>
<p><strong>一次只改一个地方</strong>。尽管工程师B怀疑另一位工程师的改动没有起到作用，但在测试时还是保留了这些改动——系统是因为已安装的改动而引发故障的，因此这些改动正是测试的目标。</p>
<p><strong>保持审计跟踪</strong>。工程师B没有找到任何说明工程师A认为问题出在噪声上的信息，也没有找到工程师A对他自己的修复所做的测试结果。或许工程师A保存了审计跟踪记录，但他留作自用了。制造过程确实需要保持审计跟踪。故障报告充分证明了内存中的数据没有错误，因为它有时无需重新加载内存也能工作。这使得工程师B能够集中精力阅读函数，从而很快找出错误的数据和丢失的读取脉冲。制造过程的测试结果也清楚地表明噪声修复并不能解决问题。工程师B记下了所有内容，包括芯片厂商中那位提供了帮助的应用工程师的姓名。</p>
<p><strong>检查插头</strong>。芯片的行为很有意思。工程师B觉得没有理由，因为他见过很多出现故障的芯片，而这块芯片很可能没有坏。他怀疑芯片的使用是否正确，而且非常肯定这是一个微妙的电源问题。</p>
<p><strong>获得全新观点</strong>。但他不知道芯片的使用是否有错误。因此他咨询了一位专家。专家知道答案，而且立即告诉了工程师B。</p>
<p><strong>没有修复的bug仍将存在</strong>。工程师A显然没有很好地测试他的修复，因为他的修复没起作用。这种尴尬的失败给了工程师B一个很好的理由，让他在编写他的工程变更清单之前，一定要确保他的修复是成功的。</p>]]></content>
        <category label="总结笔记"/>
        <category label="哲学"/>
        <category label="理念技术"/>
        <published>2025-09-14T04:21:10.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[【逻辑结构】基本ADT的遍历操作]]></title>
        <id>https://www.tinystar.me/post/9</id>
        <link href="https://www.tinystar.me/post/9"/>
        <updated>2025-09-21T14:30:50.000Z</updated>
        <summary type="html"><![CDATA[自我吐槽：唐完了，拿抽象的玩意自嗨，但连LeetCode原题都一堆写不出来。以下内容也就图一乐，多刷题才是真的。唉，眼高手低。 容器 S 上的（无重复）遍历 (traversal) 是从某个序数 α 到 S 的双射 f: α → S. 出于现实可行性考虑，α 通常是有限的，如果无限的话也只能是最小的超限序数 ω. 再大的话就没有意义了，没人能在物质世界中完成无穷多步迭代。 可迭代对象 &#x26; 迭代器 (Iterable &#x26; Iterator) 可迭代对象 (iterable)，即 逻辑结构上可转化为序列的对象。 但其实只要加上一层适配器即可，不用真创建一个新的序列——这适配器就...]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>自我吐槽：唐完了，拿抽象的玩意自嗨，但连LeetCode原题都一堆写不出来。以下内容也就图一乐，多刷题才是真的。唉，眼高手低。</p>
</blockquote>
<hr>
<p>容器 S 上的（无重复）遍历 (traversal) 是从某个序数 α 到 S 的双射 f: α → S.</p>
<p>出于现实可行性考虑，α 通常是有限的，如果无限的话也只能是最小的超限序数 ω. 再大的话就没有意义了，没人能在物质世界中完成无穷多步迭代。</p>
<h2 id="可迭代对象--迭代器-iterable--iterator" class="group"><a aria-hidden="true" tabindex="-1" href="#可迭代对象--迭代器-iterable--iterator"><span class="icon icon-link"></span></a>可迭代对象 &#x26; 迭代器 (Iterable &#x26; Iterator)</h2>
<p>可迭代对象 (iterable)，即 逻辑结构上可转化为序列的对象。</p>
<p>但其实只要加上一层适配器即可，不用真创建一个新的序列——这适配器就是「迭代器 iterator」了。</p>
<blockquote>
<p>"All problems in computer science can be solved by another level of indirection."</p>
</blockquote>
<p>例：Python 的 range；反向迭代的序列；数据流；先序/中序/后序遍历二叉搜索树……</p>
<br>
<p>「迭代」这个概念抽象了序列本质上的一系列操作。</p>
<p>当然比起终极通用的序结构还是有些额外限制的——要求有<strong>唯一的起点</strong>，并且每个元素都能通过<strong>有限次后继操作</strong>达到。不过可以没有终点，或者说终点对应超限序数 ω；此时通常使用专门的哨兵值 (sentinel) 作为标记，如 C++ 中的 <code>end()</code>.</p>
<p>首先，与引用一样，迭代器需要维护1个状态：<strong>当前元素的位置</strong>。</p>
<p>但相比更一般的引用（只支持解引用），迭代器还支持 <strong>迭代器运算</strong>，将一个迭代器变成另一个同类型迭代器，在序列上移动位置。</p>
<p>基本运算：<code>increment (next)</code>. 对应 C++ 中的 <code>Forward Iterator (ForwardIt)</code>. 例：单链表。</p>
<ul>
<li><code>++it</code></li>
</ul>
<p>逆运算：<code>decrement (prev)</code>. 对应 C++ 中的 <code>Bidirectional Iterator (BidirIt)</code>. 例：（双向）链表。</p>
<ul>
<li><code>--it</code>.</li>
</ul>
<p>重复运算：<code>random access (advance)</code>. 对应 C++ 中的 <code>Random Access Iterator (RandomIt)</code>. 例：数组。</p>
<ul>
<li><code>it ± n</code></li>
<li><code>it1 - it2</code></li>
</ul>
<p>特别地，前向迭代器还可以再限制其 读/写 功能：</p>
<ul>
<li>只能读，不能写：<code>InputIt</code>. 例：<code>stdin</code>.</li>
<li>只能写，不能读：<code>OutputIt</code>. 例：<code>stdout</code>.</li>
</ul>
<p>又例：我这是一个算幂表的输入流的迭代器，每次自乘初始值。这个显然也是只读的。</p>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="py" data-theme="github-dark-dimmed"><code data-language="py" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">def</span><span style="color:#DCBDFB"> power_iter</span><span style="color:#ADBAC7">(base):</span></span>
<span data-line=""><span style="color:#ADBAC7">    value </span><span style="color:#F47067">=</span><span style="color:#6CB6FF"> 1</span></span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#6CB6FF"> True</span><span style="color:#ADBAC7">:</span></span>
<span data-line=""><span style="color:#F47067">        yield</span><span style="color:#ADBAC7"> value</span></span>
<span data-line=""><span style="color:#ADBAC7">        value </span><span style="color:#F47067">*=</span><span style="color:#ADBAC7"> base</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#768390"># 使用</span></span>
<span data-line=""><span style="color:#F47067">for</span><span style="color:#ADBAC7"> power </span><span style="color:#F47067">in</span><span style="color:#ADBAC7"> power_iter(</span><span style="color:#6CB6FF">2</span><span style="color:#ADBAC7">):</span></span>
<span data-line=""><span style="color:#6CB6FF">    print</span><span style="color:#ADBAC7">(power)</span></span>
<span data-line=""><span style="color:#F47067">    if</span><span style="color:#ADBAC7"> power </span><span style="color:#F47067">></span><span style="color:#6CB6FF"> 100</span><span style="color:#ADBAC7">:</span></span>
<span data-line=""><span style="color:#F47067">        break</span></span><button type="button" title="Copy code" aria-label="Copy code" data="def power_iter(base):
    value = 1
    while True:
        yield value
        value *= base

# 使用
for power in power_iter(2):
    print(power)
    if power > 100:
        break" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<h2 id="关联式容器的遍历" class="group"><a aria-hidden="true" tabindex="-1" href="#关联式容器的遍历"><span class="icon icon-link"></span></a>关联式容器的遍历</h2>
<blockquote>
<p>关联式容器的遍历算法，其存在性可由「至多可数集必然可良序化（不依赖选择公理）」这一事实得以保证，具体构造方式则由存储结构决定。</p>
<p>以上。</p>
</blockquote>
<h2 id="通用-树图-遍历" class="group"><a aria-hidden="true" tabindex="-1" href="#通用-树图-遍历"><span class="icon icon-link"></span></a>通用 树/图 遍历</h2>
<figure data-rehype-pretty-code-figure=""><pre style="background-color:#22272e;color:#adbac7" tabindex="0" data-language="py" data-theme="github-dark-dimmed"><code data-language="py" data-theme="github-dark-dimmed" style="display: grid;"><span data-line=""><span style="color:#F47067">def</span><span style="color:#DCBDFB"> traverse</span><span style="color:#ADBAC7">(initial_state):</span></span>
<span data-line=""><span style="color:#768390">    # 初始化 (Initialization)</span></span>
<span data-line=""><span style="color:#ADBAC7">    frontier </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> initialize_frontier()</span></span>
<span data-line=""><span style="color:#ADBAC7">    frontier.add(initial_state)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">    while</span><span style="color:#6CB6FF"> True</span><span style="color:#ADBAC7">:</span></span>
<span data-line=""><span style="color:#768390">        # 选择 (Selection)</span></span>
<span data-line=""><span style="color:#F47067">        if</span><span style="color:#ADBAC7"> frontier.is_empty():</span></span>
<span data-line=""><span style="color:#F47067">            return</span><span style="color:#6CB6FF"> None</span><span style="color:#768390">  # 失败</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#ADBAC7">        curr_node </span><span style="color:#F47067">=</span><span style="color:#ADBAC7"> frontier.pop()  </span><span style="color:#768390"># 根据选择策略取出节点</span></span>
<span data-line=""> </span>
<span data-line=""><span style="color:#F47067">        if</span><span style="color:#ADBAC7"> curr_node.is_goal_state():</span></span>
<span data-line=""><span style="color:#F47067">            return</span><span style="color:#ADBAC7"> curr_node.answer()  </span><span style="color:#768390"># 成功</span></span>
<span data-line=""><span style="color:#ADBAC7">        </span></span>
<span data-line=""><span style="color:#768390">        # 扩展 (Expansion)</span></span>
<span data-line=""><span style="color:#ADBAC7">        frontier.push(curr_node.children)</span></span><button type="button" title="Copy code" aria-label="Copy code" data="def traverse(initial_state):
    # 初始化 (Initialization)
    frontier = initialize_frontier()
    frontier.add(initial_state)

    while True:
        # 选择 (Selection)
        if frontier.is_empty():
            return None  # 失败

        curr_node = frontier.pop()  # 根据选择策略取出节点

        if curr_node.is_goal_state():
            return curr_node.answer()  # 成功
        
        # 扩展 (Expansion)
        frontier.push(curr_node.children)" class="rehype-pretty-copy" onclick="navigator.clipboard.writeText(this.attributes.data.value);this.classList.add(&#x27;rehype-pretty-copied&#x27;);window.setTimeout(() => this.classList.remove(&#x27;rehype-pretty-copied&#x27;), 3000);"><span class="ready"></span><span class="success"></span></button><style>:root {--copy-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23adadad' d='M16.187 9.5H12.25a1.75 1.75 0 0 0-1.75 1.75v28.5c0 .967.784 1.75 1.75 1.75h23.5a1.75 1.75 0 0 0 1.75-1.75v-28.5a1.75 1.75 0 0 0-1.75-1.75h-3.937a4.25 4.25 0 0 1-4.063 3h-7.5a4.25 4.25 0 0 1-4.063-3M31.813 7h3.937A4.25 4.25 0 0 1 40 11.25v28.5A4.25 4.25 0 0 1 35.75 44h-23.5A4.25 4.25 0 0 1 8 39.75v-28.5A4.25 4.25 0 0 1 12.25 7h3.937a4.25 4.25 0 0 1 4.063-3h7.5a4.25 4.25 0 0 1 4.063 3M18.5 8.25c0 .966.784 1.75 1.75 1.75h7.5a1.75 1.75 0 1 0 0-3.5h-7.5a1.75 1.75 0 0 0-1.75 1.75'/%3E%3C/svg%3E");--success-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2366ff85' d='M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z'/%3E%3C/svg%3E");}pre:has(code) {position: relative;}pre button.rehype-pretty-copy {right: 1px;padding: 0;width: 24px;height: 24px;display: flex;margin-top: 2px;margin-right: 8px;position: absolute;border-radius: 25%;backdrop-filter: blur(3px);& span {width: 100%;aspect-ratio: 1 / 1;}& .ready {background-image: var(--copy-icon);}& .success {display: none; background-image: var(--success-icon);}}&.rehype-pretty-copied {& .success {display: block;} & .ready {display: none;}}pre button.rehype-pretty-copy.rehype-pretty-copied {opacity: 1;& .ready { display: none; }& .success { display: block; }}pre button.rehype-pretty-copy { opacity: 0; }figure[data-rehype-pretty-code-figure]:hover > pre > code button.rehype-pretty-copy {opacity: 1;}</style></code></pre></figure>
<p>这个框架的关键在于选择策略的设计。通过指定具体的选择策略，这个通用框架可以实现不同的搜索算法，比如 DFS(Depth-First Search，用stack维护状态)、BFS(Breadth-First Search，用queue维护状态)、UCS(Uniform Cost Search，用priority_queue维护状态)等。</p>
<h2 id="有根树遍历" class="group"><a aria-hidden="true" tabindex="-1" href="#有根树遍历"><span class="icon icon-link"></span></a>有根树遍历</h2>
<ul>
<li>DFS
<ul>
<li>先序 (preorder)：先访问根节点本身，再访问子树。</li>
<li>后序 (postorder)：先访问子树，再访问根节点本身。</li>
<li>二叉树的中序 (inorder)：顺序为 左子树、根节点、右子树。</li>
</ul>
</li>
<li>BFS，即 层序 (level order).</li>
</ul>]]></content>
        <category label="总结笔记"/>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-09-21T14:30:50.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[【逻辑结构】基本ADT的基本概念&基本操作]]></title>
        <id>https://www.tinystar.me/post/8</id>
        <link href="https://www.tinystar.me/post/8"/>
        <updated>2025-09-18T09:36:48.000Z</updated>
        <summary type="html"><![CDATA[data structure := collection that keeps some properties ADT := Abstract Data Type = data structure without storage structure = logical structure 因此 ADT 一般不考虑性能，比较接近于纯数学的结构。这样，理解起来会简单很多很多，关注有哪些 操作 (operations) 即可。 将ADT上的操作分为以下3类： 基本操作，公理级的，定义ADT时就会赋予，无需额外实现。e.g. random access. ADT自然衍生出的简单算法问题，一般有标准答案...]]></summary>
        <content type="html"><![CDATA[<p>data structure := collection that keeps some properties</p>
<p>ADT := Abstract Data Type = data structure without storage structure = logical structure</p>
<p>因此 ADT 一般不考虑性能，比较接近于纯数学的结构。这样，理解起来会简单很多很多，关注有哪些 操作 (operations) 即可。</p>
<br>
<p>将ADT上的操作分为以下3类：</p>
<ol>
<li>基本操作，公理级的，定义ADT时就会赋予，无需额外实现。e.g. <code>random access</code>.</li>
<li>ADT自然衍生出的简单算法问题，一般有标准答案，并且可能不学也能想出来。其意义在于，这层抽象能降低思考更复杂算法时的心智负担，调用现成算法也能让人少写点样板代码。e.g. <code>reverse</code>.</li>
<li>可单独作为一个专题介绍。是算法设计研究的核心。e.g. <code>sort</code>.</li>
</ol>
<br>
<p>提取出3类个人认为是最基础的ADT：Associative (包含set和map), Sequence, Tree/Graph.</p>
<blockquote>
<p>此分类和具体用词的最初灵感来自 <a href="https://en.cppreference.com/w/cpp/container.html" rel="nofollow noopener noreferrer" target="_blank">Containers library - cppreference.com</a>.</p>
</blockquote>
<p>可以注意到其与数学中集合论语言的完美对应。set, map, sequence自不必说，而 graph 其实就是二元关系。</p>
<p>不同之处在于，数学里这几种结构可以随便互相实现；比如只需公理化定义集合论，其他结构就都能用集合构造出来。而编程语言里由于资源有限，一般分得比较开；比如大多数编程语言不会将“纯函数”和“关联数组”合二为一，也不会将“数组”视作“从自然数到元素的映射”。但我们应当仍然能注意到，逻辑上它们是互通的。</p>
<h2 id="1-关联-associative" class="group"><a aria-hidden="true" tabindex="-1" href="#1-关联-associative"><span class="icon icon-link"></span></a>1. 关联 (Associative)</h2>
<table>
<thead>
<tr>
<th>名称</th>
<th>内容</th>
</tr>
</thead>
<tbody>
<tr>
<td>集合 (Set)</td>
<td>可实现为「定义域为全集、陪域为 <code>{true, false}</code> 的映射」（示性函数）。<br>或者对空类型支持得好的话，也可实现为 <code>Map&#x3C;Key, ()></code>（表示函子）；比如 Rust 的 <code>Set&#x3C;Key></code> 就是这样实现的。<br>核心操作：<code>contains(key) -> bool</code>.<br>辅助操作：<code>insert</code>, <code>remove</code>.</td>
</tr>
<tr>
<td>映射 (Map)</td>
<td>也称「关联数组 associative array」；Python 中也称作「字典 dictionary」。<br>可实现为「键值对的集合」，即 <code>Set&#x3C;(Key, Value)></code>.<br>核心操作：<code>get(key) -> Option&#x3C;&#x26;Value></code>.<br>辅助操作：<code>insert</code>, <code>remove</code>.</td>
</tr>
</tbody>
</table>
<p>扩展：<code>MultiSet</code>, <code>MultiMap</code>.</p>
<p>衍生 TODO:</p>
<ul>
<li>【存储结构】Associative: ordered vs hash【索引】</li>
<li>【算法专题】Associative: Utils</li>
</ul>
<h2 id="2-序列-sequence" class="group"><a aria-hidden="true" tabindex="-1" href="#2-序列-sequence"><span class="icon icon-link"></span></a>2. 序列 (Sequence)</h2>
<p>又称「线性表」。</p>
<p>序列的 下标(subscript) / 索引(index) 相当于 映射的 key，序列可以实现为以自然数作为 key 的映射。</p>
<blockquote>
<p>像 Lua 语言就把它们统一成了一种，叫"table".</p>
</blockquote>
<p>序列相对于映射的本质区别就在于「序」——用映射实现序列的话，可以认为这个序来自自然数的序结构。</p>
<p>序列的基本操作：</p>
<ul>
<li>顺序访问 (sequential access).
<blockquote>
<p>前驱 (prev)、后继 (next).</p>
</blockquote>
</li>
<li>随机访问<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup> (random access). <code>a[i] (0≤i&#x3C;n)</code>，索引 <code>i</code> 可理解为该节点的「前驱 的数量」、「相对于起点的偏移量 (offset)」。</li>
<li>在指定位置插入元素 (insert)
<blockquote>
<p><code>push_front</code>, <code>push_back</code></p>
</blockquote>
</li>
<li>移除指定位置的元素 (erase)
<blockquote>
<p><code>pop_front</code>, <code>pop_back</code></p>
</blockquote>
</li>
</ul>
<p>衍生 TODO:</p>
<ul>
<li>【存储结构】Sequence: contiguous vs linked</li>
<li>【算法专题】Sequence: Utils</li>
</ul>
<h2 id="3-树图-treegraph" class="group"><a aria-hidden="true" tabindex="-1" href="#3-树图-treegraph"><span class="icon icon-link"></span></a>3. 树/图 Tree/Graph</h2>
<p><strong>图</strong> 的定义：考虑顶点集合<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span>，边集合<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi></mrow><annotation encoding="application/x-tex">E</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span></span></span></span>，其中 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span>为非空集合，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi></mrow><annotation encoding="application/x-tex">E</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span></span></span></span>为<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span>上的关系，则称<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mo>=</mo><mo stretchy="false">(</mo><mi>V</mi><mo separator="true">,</mo><mi>E</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">G=(V, E)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">G</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mclose">)</span></span></span></span>为集合<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span></span></span></span>上的图。</p>
<p><strong>树</strong> 通常有2种等价的定义方式：</p>
<ol>
<li>递归定义。</li>
<li>将树定义为一种特殊的图。</li>
</ol>
<p>递归定义：</p>
<ol>
<li>归纳递推：树由 1个<strong>根节点</strong> 和 若干棵互不相交的<strong>子树</strong> 组成。</li>
<li>归纳奠基：空集是树（空树）。</li>
</ol>
<p>将树定义为一种特殊的无向图，具体有多种等价的定义，比如：</p>
<ol>
<li><strong>路径唯一性</strong>。任意两个顶点间有且仅有一条路径的图。</li>
<li><strong>无环连通图</strong>。</li>
<li><strong>极小连通图</strong>。移除任意一条边都会使其不再连通；即，每条边都是桥。</li>
<li><strong>极大无环图</strong>。任意添加一条边都会产生环。</li>
</ol>
<p>以上两类定义的主要差别在于，递归定义有一个特殊的根节点（有根树），而将树定义为特殊的无向图就没有根节点（无根树）。有根树确定了节点间的<strong>层次结构</strong>。</p>
<p>无根树中任意节点都可以被指定为根。一旦你选择了一个特定的节点作为根，这棵无根树就变成了一棵有根树。</p>
<p>若干棵互不相连的树组成“森林”。也可将森林定义为：每个连通分量都是树的图。</p>
<p>部分特殊的树：</p>
<ul>
<li><code>d</code>叉树：每个节点最多 <code>d</code> 个子节点的有根树。一般会对每个子节点的顺序加以区分。</li>
<li>真<code>d</code>叉树：每个节点要么没有子节点，要么<code>d</code>个子节点。换言之，子树要么全空要么全非空。</li>
<li>完全<code>d</code>叉树：完全树的形状由节点个数唯一确定，按层序逐个添加节点。</li>
<li>满<code>d</code>叉树：是完全树，并且每层都填满了（从第0层根节点开始，第<code>n</code>层节点数为<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>d</mi><mi>n</mi></msup></mrow><annotation encoding="application/x-tex">d^n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span>）。</li>
</ul>
<p>基本操作：</p>
<ul>
<li>访问当前节点</li>
<li>访问子节点/后继节点</li>
<li>访问父节点/前驱节点</li>
</ul>
<p>衍生 TODO:</p>
<ul>
<li>【存储结构】Tree: 层序数组表示</li>
<li>【算法思想】状态转移</li>
</ul>
<p>没有限制的「集合」是简单的，完全限制住了的「序列」也是简单的；而 有限制、但不完全有 的「图」，那可就太复杂了。@「<strong>图论</strong>」。</p>
<p>TODO: 图论算法</p>
<section data-footnotes class="footnotes"><h2 class="sr-only group" id="footnote-label"><a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>这个翻译其实有点误导，译成「随意」、「任意」更易于正确理解。不过「随机」也行吧，而且已是事实标准。 <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content>
        <category label="总结笔记"/>
        <category label="DSA/数据结构与算法/基于规则的算法"/>
        <published>2025-09-18T09:36:48.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[交互式 Bash Shell 技巧精粹]]></title>
        <id>https://www.tinystar.me/post/5</id>
        <link href="https://www.tinystar.me/post/5"/>
        <updated>2025-09-09T15:17:42.000Z</updated>
        <summary type="html"><![CDATA[编辑当前命令 Bash 默认使用 Emacs 快捷键. macOS 本身近乎全局支持 Emacs 快捷键，因此 macOS 用户应该会很适应。 键盘的左右箭头不好按的话，可以用 Emacs 快捷键 C-b (back)、C-f (forward). 将 Ctrl 换成 Alt (Meta) 就是以 单词 为单位跳转，对应 Windows 的 Ctrl+左/右箭头. C-a, C-e ，分别对应 Windows 的 home， end. C-u, C-k，分别清空光标前/光标后的内容. 配合 C-a, C-e 使用可以实现 清空当前命令。 再补充：Ctrl + _` 是撤销。 至于其他的快捷键，...]]></summary>
        <content type="html"><![CDATA[<h2 id="编辑当前命令" class="group"><a aria-hidden="true" tabindex="-1" href="#编辑当前命令"><span class="icon icon-link"></span></a>编辑当前命令</h2>
<p>Bash 默认使用 Emacs 快捷键. macOS 本身近乎全局支持 Emacs 快捷键，因此 macOS 用户应该会很适应。</p>
<p>键盘的左右箭头不好按的话，可以用 Emacs 快捷键 <kbd>C-b</kbd> (back)、<kbd>C-f</kbd> (forward). 将 <kbd>Ctrl</kbd> 换成 <kbd>Alt (Meta)</kbd> 就是以 单词 为单位跳转，对应 Windows 的 <kbd>Ctrl+左/右箭头</kbd>.</p>
<p><kbd>C-a</kbd>, <kbd>C-e</kbd> ，分别对应 Windows 的 <kbd>home</kbd>， <kbd>end</kbd>.</p>
<p><kbd>C-u</kbd>, <kbd>C-k</kbd>，分别清空光标前/光标后的内容. 配合 <kbd>C-a</kbd>, <kbd>C-e</kbd> 使用可以实现 清空当前命令。</p>
<p>再补充：<kbd>Ctrl + _`</kbd> 是撤销。</p>
<br>
<p>至于其他的快捷键，在Shell的一行里折腾属于是螺蛳壳里做道场，至少我觉得非常麻烦，不如直接启动熟悉的编辑器，想怎么改就怎么改——</p>
<p><kbd>C-x C-e</kbd>: 启动默认编辑器 编辑当前命令。编辑完毕、保存退出后即执行。</p>
<p>如果默认编辑器是 <code>nano</code>的话，可考虑先 <code>export VISUAL=vim</code> 将默认编辑器设成 vim.</p>
<p>我个人喜欢 vim 远胜过 nano。而且在这个场景下 nano 还有个无法容忍的缺点：vim 可 <code>:cq</code> 以错误状态退出，这样命令就不会被执行。nano 似乎不支持这一点。</p>
<h2 id="命令内复用" class="group"><a aria-hidden="true" tabindex="-1" href="#命令内复用"><span class="icon icon-link"></span></a>命令内复用</h2>
<p><code>{}</code></p>
<p>用逗号分隔多个选项，例：</p>
<ul>
<li><code>mkdir project/{src,test,docs}</code> => <code>mkdir project/src project/test project/docs</code></li>
<li><code>mv longlonglongname.{old,new}</code> => <code>mv longlonglongname.old longlonglongname.new</code></li>
<li><code>cp file{,.bak}</code> => <code>cp file file.bak</code></li>
</ul>
<p>用 <code>..</code> 表示序列范围，例：</p>
<ul>
<li><code>touch log{01..12}.txt</code> => <code>touch log01.txt log02.txt log03.txt log04.txt log05.txt log06.txt log07.txt log08.txt log09.txt log10.txt log11.txt log12.txt</code></li>
</ul>
<p>处理那些有规律但又有细微差别的操作时非常方便。</p>
<h2 id="命令间复用" class="group"><a aria-hidden="true" tabindex="-1" href="#命令间复用"><span class="icon icon-link"></span></a>命令间复用</h2>
<p>最简单的复用：按上箭头。键盘的上下箭头不好按的话，可以用 Emacs 快捷键 <kbd>C-p</kbd> (previous)、<kbd>C-n</kbd> (next).</p>
<p><code>!!</code>: 上一条命令。</p>
<p>例：执行命令发现权限不够，则 <code>sudo !!</code>.</p>
<br>
<p><code>!&#x3C;prefix></code>: 以 <code>&#x3C;prefix></code> 为前缀的上一条命令。</p>
<p>注意这里的<code>&#x3C;prefix></code> 不含空格。如想用 <code>!bash ./</code>匹配最近的以<code>bash ./</code>开头的命令是不对的，会匹配成最近的以<code>bash</code>开头的命令，再拼接上<code> ./</code>。</p>
<p>这种情况下用 <kbd>C-r</kbd> 是最方便的，匹配前缀、渐进式搜索。</p>
<p>乃至 <code>history | grep &#x3C;xxx></code>，在历史记录中一般化的搜索。</p>
<br>
<p><code>!&#x3C;num></code>: 编号为<code>&#x3C;num</code>的历史命令。</p>
<p>配合 <code>history | grep</code> 之类搜索历史记录的还算有用，查到之后记住其编号即可，无需把旧命令再复制粘贴一遍。</p>
<br>
<p>提取命令中的一部分：</p>
<table>
<thead>
<tr>
<th>语法</th>
<th>语义</th>
<th>助记</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>!$</code></td>
<td>上一条命令的末参数</td>
<td>想想 $ 在正则表达式中的含义</td>
</tr>
<tr>
<td><code>!^</code></td>
<td>上一条命令的首参数（是参数不是命令名）</td>
<td>想想 ^ 在正则表达式中的含义</td>
</tr>
<tr>
<td><code>!*</code></td>
<td>上一条命令的所有参数</td>
<td>想想 * 作为通配符的含义</td>
</tr>
</tbody>
</table>
<p>将 首 和 末 一般化为「索引」，则得<code>!:&#x3C;index></code>. 命令名的索引为0.</p>
<p>进一步一般化为「切片」，则得 <code>!:&#x3C;begin>-&#x3C;end></code>. 左闭右闭。特别地，<code>!:0-</code> 就是除了最后一个参数之外的全部。</p>
<br>
<p><code>^old^new</code>: 将上一个命令中 首次出现的 <code>old</code> 替换成 <code>new</code>.</p>
<p>例：玩 <a href="https://overthewire.org/wargames/bandit/" rel="nofollow noopener noreferrer" target="_blank">https://overthewire.org/wargames/bandit/</a> 时，关卡编号<code>&#x3C;num></code>对应用户名 <code>bandit&#x3C;num></code>。登录初始关卡的命令是 <code>ssh bandit0@bandit.labs.overthewire.org -p 2220</code>，则 <code>^0^1</code> 即得 <code>ssh bandit1@bandit.labs.overthewire.org -p 2220</code>, 再接下来 <code>^1^2</code>、<code>^2^3</code>即可，很方便。</p>
<br>
<p>最后，可能你会想要设置 <code>shopt -s histverify</code>——Bash 的默认行为是自动直接执行匹配到的命令；启用这个的话，则只是将当前命令填充为匹配到的命令，可以确认、乃至进一步修改之后，再手动回车执行。</p>]]></content>
        <category label="总结笔记"/>
        <category label="计算机技术"/>
        <published>2025-09-09T15:17:42.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[【资治通鉴导读-课程论文】长平之战]]></title>
        <id>https://www.tinystar.me/post/4</id>
        <link href="https://www.tinystar.me/post/4"/>
        <updated>2025-06-17T02:17:04.000Z</updated>
        <summary type="html"><![CDATA[作于 2022年秋 在大学的第三个秋季，不过由于降转，是大二。 当时处于长期“慢性”的状态不好，拖延症极其严重（说得狠点的话，确实是很犯贱），事实上一直持续到25年还没好。拖到ddl后才赶着写完补交，好在助教人很好；后来疫情防控形势严峻，还指点我如何做好准备，应对大概率会有的封楼。 事实上，选题选长平之战，有很大原因是为了方便吃老底——我对春秋战国的历史相对熟悉（主要来自《东周列国志》），写起来比较快orz。 这门课还是挺有意思的，而且工作量下限小、给分好。在选这门课前我买了一套《资治通鉴》，但选上课之后反而没怎么看了，只在要写论文时才仔细读了相关部分。突出一个别扭，经常选上课后反而有些不想学...]]></summary>
        <content type="html"><![CDATA[<p>作于 2022年秋</p>
<p>在大学的第三个秋季，不过由于降转，是大二。</p>
<p>当时处于长期“慢性”的状态不好，拖延症极其严重（说得狠点的话，确实是很犯贱），事实上一直持续到25年还没好。拖到ddl后才赶着写完补交，好在助教人很好；后来疫情防控形势严峻，还指点我如何做好准备，应对大概率会有的封楼。</p>
<blockquote>
<p>事实上，选题选长平之战，有很大原因是为了方便吃老底——我对春秋战国的历史相对熟悉（主要来自《东周列国志》），写起来比较快orz。</p>
</blockquote>
<p>这门课还是挺有意思的，而且工作量下限小、给分好。在选这门课前我买了一套《资治通鉴》，但选上课之后反而没怎么看了，只在要写论文时才仔细读了相关部分。突出一个别扭，经常选上课后反而有些不想学了，哎。</p>
<hr>
<p>长平之战，是战国末期的一场著名的大规模战役。在这场战役中，秦国、赵国都出动了倾国之师，最终秦军战胜赵军，并将40万赵降卒尽数坑杀。决战中，赵国主将赵括死读兵书、葬送赵国大军的故事更凝成了后世知名的成语“纸上谈兵”。此战之后，赵国国力大损，彻底失去独立对抗秦国的能力；而30余年后，赵国与其他五国便陆续被秦国消灭。可以说，这场战役是战国局势中的一个关键节点。</p>
<p>公众一般认为，长平之战中，赵国之所以惨败，是因为中了秦国的反间计，将老将换成了纸上谈兵的赵括。这一看法基本正确，但并不足以概括长平之战的始末。本文将按时间顺序分析论述长平之战的大致过程，并分别分析秦国、赵国在庙堂之上、沙场之中的得失，最后论述长平之战对战国局势的影响与后续事件。</p>
<p>《资治通鉴》卷5《周纪五》，周赧王五十三年至五十六年。</p>
<h2 id="战役进程" class="group"><a aria-hidden="true" tabindex="-1" href="#战役进程"><span class="icon icon-link"></span></a>战役进程</h2>
<p>秦国、赵国爆发此战的直接冲突在于韩“上党十七邑”。赧王五十三年，秦国白起伐韩，攻下野王，断绝了上党与韩国国都新郑的联系，眼看就要吞下上党。上党郡守冯亭不愿降秦，主动献上党十七邑给赵国，希望能让赵国代替韩国受兵。赵王有意接受，而平阳君赵豹认为秦国付出了攻杀的劳苦而赵国却得到了相应好处，会得罪秦国，危险；而平原君赵胜认为上党十七邑属大利，也觉得应当接受。于是赵王派平原君前往受地，对上党吏民大加赏赐。</p>
<p>不管是恐惧秦法严苛而不愿降秦，还是看到秦国贪得无厌希望能将赵国绑上抵抗秦国入侵的战车，冯亭所献的上党十七邑作为一个巨大的诱饵——十七邑不是小数目，秦王哄骗和氏璧时，也只开出了十五城；而且上党高地地形险要、扼南北交通要冲、靠近赵国首都邯郸，其价值非普通的十七城可以衡量——都事实上起到了将秦国的怒火转移到了赵国上的效果。对韩国来说，这无疑是驱虎吞狼、促成两大强邻争斗的好棋，让韩国多苟延残喘了几十年。而对赵国来说，从后续长平之战赵国所受的惨痛教训来看，受上党似乎是利令智昏、是招致强秦打击的败招；但若真将上党拱手让秦，秦国经营好上党后，居高临下威逼邯郸，对赵国来说也是极其严重的威胁。这一战略要地对赵国来说确实重要，因此要说赵国收下上党是失策，倒也未必如此。如果做个类比，就像是新中国不得不抗美援朝，赵国也不得不接手上党十七邑。</p>
<p>但致命的是，赵国似乎严重低估了秦国吞下上党的决心。上党对赵国来说是大利，对秦国来说也是。“秦服其劳而赵受其利”，秦怒而赵喜，秦国决心要将上党从赵国中抢回，而赵国却还怀着秦国可能会放弃的想法、没有做充足的准备。“多算胜，少算不胜”，在开战前，赵国就已经埋下了严重的败因。</p>
<p><strong>战略上的重视不足</strong>，暗示了赵国后续的重大失误，也最终将赵国拖入了两难的困境。</p>
<p>秦国没有立即动手。过了两年，赧王五十五年，秦国王龁出兵攻克上党，上党百姓逃奔赵国。赵国廉颇屯兵长平，支援上党。</p>
<p>这一部分就展现出了秦赵二国对此战的准备程度之差异。秦国可以说是充分重视赵国，没有在攻韩之后立刻攻赵。秦国在韩国野王城附近继续打通路线，确保<strong>后勤</strong>畅通。而赵国受上党后，大赏吏民，以万户都三封太守、以千户都三封县令，沉浸在受地的喜悦中，却没有充分重视随时可能攻来的秦军，没有做好“大决战”的准备。</p>
<p>战初，相对准备不足的赵军连连败退。赵国感受到了压力，意识到可能无法在作战中取胜，派重使郑朱向秦国请和。</p>
<p>可以说，这是赵国在长平之战中的致命失误。一方面，这体现了赵国至此仍对秦国抱有幻想，认为秦国可能会放弃上党；而另一方面，“请和”的行为导致了赵国在长平之战中的孤立无援。</p>
<p>如虞卿所说，各国在秦国都有使者，一旦赵使求和，秦国必然大肆宣扬。各国见到赵国并没有决战到底的意思，必然就不愿意救援：万一帮了赵国，结果赵国却撑不住了，那自己不就给秦国留了口实吗？果然，秦国高规格接待赵使者，却不肯议和。诸国害怕秦赵议和后连横针对自己，反而于己不利，不敢支援赵国。</p>
<p>本来，如果赵国派使者出重宝拉拢楚、魏，则秦国自然担心楚、魏在关键时刻出手，此时赵国有他国应援，才能在外交谈判上取得与秦国平等的位置。而此时，<strong>赵国自己将外援推到了对立面</strong>，可悲可叹。</p>
<p>此时，其实赵国的失败已经几乎注定。秦国商鞅变法之后，富国强兵，秦民力于耕战。赵国胡服骑射，士兵勇猛或许不输强秦，但<strong>粮食产量</strong>却远不如秦国。本来，秦国作为攻方、赵国作为守方，似乎应当是秦国补给线远长于赵国，更难支撑下去；但秦国经营有方，所吞并的土地有力维护了秦军的补给线，而赵国近乎仓促迎战，准备不足，粮食储备逊于秦国。在两三年的战略对峙之下，守方的赵国反而先支撑不住了。</p>
<p>沙场之后，双方的后勤压力都越来越大。但先<strong>沉不住气</strong>的是赵国。一来，赵国的国力确实不如秦国，可从事生产的人口不足、粮食储备不足，在这样的战略对峙中极为吃亏。二来，上党与邯郸隔着太行山，陆运不便，与秦国的河东、河内则是水陆相连，具有水运优势，此消彼长之下赵的后勤支撑难度明显增大。三来，赵国的宗室本就对“是否要接手上党”存在完全不同的意见，并非所有人都支持赵王的看法，远不如秦国团结。这些都给了赵王极大的压力，他迫切地渴望“速胜”，而廉颇战初的连败与之后的拒绝出战都让赵王不满。</p>
<p>赵国<strong>政坛不合</strong>给了秦国施展“离间”的机会。应候派人在邯郸散布谣言，称“秦国只害怕马服君之子赵括为将”、“廉颇无用、将降秦”。四起的谣言正暗合了赵王的心意，于是他力排众议，以赵括代替廉颇为将。</p>
<p>赵王选赵括为将，其实也有一定的理由。在粮食不足的巨大压力下，他只好选择放手一搏；而廉颇始终拒绝出战，那就只好换将——速战速决的风险实在太大，老将们都不愿意承受如此大的风险。在之前与秦对决的阏与之战里 ，廉颇等老将也曾拒绝出兵，而马服君赵奢力排众议，提出“将勇者胜”，并最终率领赵军大败秦军。赵王信服赵奢延及其子赵括，又听信四处传言，便将一切希望都托在了赵括，指望他重现父辈的传奇。但“王以为如其父”，实则“父子异心”，赵括虽然熟读兵书，却<strong>并不真具有打好这一场决定国家命运的大仗的能力</strong>。</p>
<p>秦作为攻方，虽然准备得更充足、国力更为强盛，但在长期对峙之下，后勤压力也不可小视。然而关键的是，秦国不像赵国沉不住气，始终表现得胸有成竹，没有暴露自身弱点。而秦国通过情报工作捕捉到了赵国的弱点，利用赵王的急于求成，因势利导，让赵国换上了缺乏实战经验的赵括，让战争的天平倒向己方。</p>
<p>针对赵括，秦军换上了作战经验丰富的白起为将，并做了严格的保密工作，赵国完全没有察觉。战役的结果至此已几乎注定。</p>
<p>赵括贯彻了赵王“速战速决”的想法，主动出击。秦军佯装败退，吸引赵军深入。于是，赵军主力被调动，企图攻破秦营垒。于是，赵军的补给线失去了防守。秦军派出两支奇兵迂回包抄，一支截断赵粮道，一支将赵军主力与大本营隔绝。秦军营垒岂是轻易就能攻破的？赵军主力未取得所求的战果，却断绝了粮草。而之前修筑的坚固的防御线，此时却被秦军用来封锁赵军自己。赵军战斗不利，便筑壁坚守，等待救援。</p>
<p>《孙子兵法》有言：“致人而不致于人”。秦国情报工作优秀，充分利用了赵括急于求成的心理，诱其忽略了对后勤的防守，一举夺得了胜势。</p>
<p>赵国高层也陷入恐慌，向齐国请求粮食支援。齐国拒绝了。诸国也没有出动有力的救援。秦王更亲自渡过黄河，来到河内，动员十五岁以上的男丁全部开往前线，加强防线，遮绝赵救兵及粮食。</p>
<p>赵国大军无援无粮，反复冲击包围，都没能打通粮道。赵军断粮四十六天，沦落到了暗中互相杀人吃的程度。赵括亲自组织最后的精锐冲击，最终被射杀，剩余的数十万赵军投降。</p>
<p>赵括的总体表现无疑是令人大跌眼镜的：若是正面交锋，赵军绝不至于败得如此惨烈。轻敌被围后又畏敌，空耗粮草、放任秦国加强包围，一直拖到不能再拖才全力冲击。</p>
<p>但他也有值得认可的一面：被围四十六日，军队始终没有崩溃、未发生兵变，仍能冲击重创秦军（“秦虽胜于长平，士卒死者过半，国内空” ，长平之战以战略相持为主，秦军伤亡中的大部分只能来自最后决战），直到赵括死后才投降。固然可能相当一部分要归功于其统帅身份与赵卒的训练有素，但至少能看出来，赵括是有不错的治军能力的，至少足以担任普通将领的职位。但让此时的他统帅三军、主导大战争，无疑是个大错。</p>
<h2 id="总结败因" class="group"><a aria-hidden="true" tabindex="-1" href="#总结败因"><span class="icon icon-link"></span></a>总结败因</h2>
<p>总结长平之战中赵败秦胜的主要原因，可大略归纳如下：</p>
<p>1、赵国对此战重视不足，缺乏战略定力。</p>
<p>上党对赵国来说是重要的战略要地，受上党是自然之举，就像新中国不得不抗美援朝。但在秦国虎口下夺了食，就应全方面做好与秦国决一死战的准备。但赵国表现得不尽如人意：政坛意见不合，军事、后勤也没有提前做好大决战的准备。</p>
<p>可以说，赵国领导集团没意识到此战的重要性，在求战与求和中反复摇摆。战役开始一段时间，又不想打了，幻想能轻易和秦国议和。议和失败、长期对峙之下，又沉不住气，急于求成。</p>
<p>2、严重的外交失误。</p>
<p>对此战的重视不足也相当程度上导致了赵国严重的外交失误。</p>
<p>赵国本应意识到这是秦赵之间的一场大战，可以联合魏、楚，使它们在南方向秦施压，使秦国畏惧诸国合纵，增强己方实力。但赵国轻率的请和，使他国犹疑赵国的态度，造成了赵国的孤立无援。</p>
<p>而赵国向齐国请援时，没有充分考虑到五国灭齐带来的仇恨，只以道理相劝。若肯多一些诚意，归还一部分土地给齐国，也大有希望能争取到齐国的粮食和兵力支援，为大战增添筹码。</p>
<blockquote>
<p>不过这可能也太晚了。</p>
</blockquote>
<p>3、赵国与秦国的国力差距。</p>
<p>当时，赵国是唯一未遭受过秦国的重大战略打击的国家，军事实力不可小觑。但在国力上，商鞅变法、司马错平蜀后的秦国，粮食产量是远胜过赵国的。在极其考验国力的战略对峙中，赵国天然存在劣势。</p>
<p>商鞅变法也使得秦国的国家治理体系远强于六国，具有强大的作战动员能力。</p>
<p>4、执政体系的差距。</p>
<p>战略相持期间，双方都在等待敌方生变。双方都有巨大的压力，而赵国相比秦国更加沉不住气，屡次做出不合理的决策。秦国的情报系统能够了解到赵国的重要情报，而赵国却无法得知秦国的重要情报，受制于人。</p>
<p>5、后勤压力大。</p>
<p>在战前准备、外交、国力上的严重不足，严重削弱了身为守方的赵国本应具有的后勤优势。而地形上，上党与邯郸有太行山相隔，陆运不便；秦国却可以从河东、河内水运粮食，减少陆路距离。</p>
<p>6、在决战中换上了缺乏实战经验的赵括。</p>
<p>易将赵括之时，赵国的落败已经难免。但若无赵括轻率闯进秦军陷阱，赵军至少不至于全军覆没、断送赵国生力。</p>
<h2 id="对战国局势的影响与后续事件" class="group"><a aria-hidden="true" tabindex="-1" href="#对战国局势的影响与后续事件"><span class="icon icon-link"></span></a>对战国局势的影响与后续事件</h2>
<p>在长平之战前，韩、魏、齐、楚、燕五国都曾遭受过严重打击，国力大损；如齐国伐燕，乐毅合五国灭齐，韩、魏、楚则都受过秦国的疯狂进攻。而赵国则是六国中唯一尚未受到严重打击的国家。而长平之战后，赵国也遭受到了重创，元气大伤，再也无力独立和秦国对抗。三十余年后，秦国陆续灭掉了关东六国。</p>
<p>长平之战也激起了各国对秦的不满、对赵的同情，促成了六国一段时间内的强烈反扑。</p>
<p>长平之战后，秦军也损失惨重。白起本打算一鼓作气攻下邯郸，但被韩、魏派苏代游说应候，让韩割一城、赵割六城求和，停战。赵王听从虞卿意见，不割六城给秦国，而是贿赂齐国，并结好韩、魏。</p>
<p>第二年，秦重新出兵攻打邯郸。赵国一边坚守邯郸，一边遣使向魏、楚求救，最终魏公子无忌合诸侯之兵，在邯郸城下大败秦军。</p>
<p>由此亦可见，若赵国能够充分重视长平之战、联合诸国，很可能就不会在长平之战中沦落到被迫速战速决、招致惨败的程度，甚至很有希望顺利吞下上党。长平之战的惨败，根源在于赵国的庙堂之上，而非沙场。</p>
<p>秦庄襄王三年，秦军再次攻克上党，又帅师伐魏。公子无忌合纵五国联军在河外大败秦军，追击至函谷关。这便是长平之战后，六国最后的有力反扑。</p>]]></content>
        <category label="把多余的事情也探索了吧"/>
        <category label="过去记录"/>
        <category label="人文"/>
        <published>2025-05-12T15:05:21.000Z</published>
    </entry>
</feed>