<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://jinchaoli.com/feed.xml" rel="self" type="application/atom+xml"/><link href="https://jinchaoli.com/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-05-09T13:59:59+08:00</updated><id>https://jinchaoli.com/feed.xml</id><title type="html">Jinchao Li</title><subtitle>Jinchao Li&apos;s Homepage</subtitle><entry><title type="html">Thinking with Visual Primitives (转)</title><link href="https://jinchaoli.com/blog/thinking-with-visual-primitives-(%E8%BD%AC)/" rel="alternate" type="text/html" title="Thinking with Visual Primitives (转)"/><published>2026-05-01T00:00:00+08:00</published><updated>2026-05-01T00:00:00+08:00</updated><id>https://jinchaoli.com/blog/thinking-with-visual-primitives-(%E8%BD%AC)</id><content type="html" xml:base="https://jinchaoli.com/blog/thinking-with-visual-primitives-(%E8%BD%AC)/"><![CDATA[<p>作者：PaperAgent</p>
<p>链接：https://zhuanlan.zhihu.com/p/2033494636023559146</p>
<p>来源：知乎</p>
<p>著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</p>
<p>上周<strong>DeepSeek V4</strong>发布了，但遗憾的还是没有<strong>多模态</strong>，今天（老规矩，节假日发布）DeepSeek把这块补上了，开源了最新的多模态技术&amp;Paper：<strong>Thinking with Visual Primitives</strong>（以视觉原语思考）</p>
<p><img src="https://pic2.zhimg.com/v2-027e592355913ad24b2c9734dcd369e5_1440w.jpg" alt=""/></p>
<p><strong>DeepSeek&amp;北京大学&amp;清华大学</strong>提出”视觉基元推理”框架，将边界框与坐标点提升为”最小思考单元”，解决MLLM在复杂空间推理中的<strong>Reference Gap</strong>（指代鸿沟）问题。基于<strong>DeepSeek-V4-Flash</strong>构建的模型，在仅使用约90个KV Cache视觉token的情况下，性能比肩<strong>GPT-5.4、Claude-Sonnet-4.6与Gemini-3-Flash</strong>。<a href="https://link.zhihu.com/?target=https://mp.weixin.qq.com/s?__biz=Mzk0MTYzMzMxMA==&amp;mid=2247507031&amp;idx=1&amp;sn=d7081d9cb42cf4bbf3febc3cdd439fcf&amp;scene=21#wechat_redirect">Skills驱动推理新范式，清华&amp;北大：Token立省59%</a></p>
<p><img src="https://pic2.zhimg.com/v2-67e3d69aecfb10f8eb6e14d15e2e6a07_1440w.jpg" alt=""/></p>
<h2 id="一从感知鸿沟到指代鸿沟问题重新定义"><strong>一、从感知鸿沟到指代鸿沟：问题重新定义</strong></h2>
<p>当前多模态大语言模型（MLLM）的Chain-of-Thought（CoT）推理几乎完全发生在<strong>语言空间</strong>。即便前沿模型通过高分辨率裁剪、动态分块等策略解决了”看不清”的<strong>Perception Gap</strong>（感知鸿沟），它们在面对密集计数、拓扑导航、多步空间推演时，仍然频繁出现<strong>逻辑崩塌（logical collapse）</strong>。</p>
<p>DeepSeek团队指出，这背后是一个更本质的瓶颈——<strong>Reference Gap（指代鸿沟）</strong>：</p>
<blockquote>
<p>自然语言天生是模糊、连续的，而视觉空间是精确、离散的。当模型用语言描述”左边第二个红色的物体”时，它实际上已经丢失了精确的空间锚点，导致推理链条与图像实体脱节，最终引发级联幻觉。</p>
</blockquote>
<p>人类是如何解决这个问题的？我们在数一堆密集物体或走迷宫时，会本能地<strong>用手指指向目标</strong>，将抽象的语义概念锚定到具体的物理坐标上，大幅降低工作记忆负担。</p>
<p>受此启发，论文提出<strong>Thinking with Visual Primitives（基于视觉基元的思考）</strong>：将<strong>边界框（bounding boxes）</strong>和<strong>点（points）</strong>提升为与语言token同级的”最小思考单元”，直接交错插入模型的推理轨迹中。模型不再是”说完再指”，而是<strong>边指边想（point while it reasons）</strong>。</p>
<p><img src="https://pic1.zhimg.com/v2-cd0c386e5ad61f84cb57ddacebc1d28c_1440w.jpg" alt=""/></p>
<p>Figure 1对比了各模型在800×800分辨率下的KV Cache Entries与7项基准平均分</p>
<p>Figure 1揭示了这一范式的惊人效率：对于800×800的输入，该模型在KV Cache中仅保留约<strong>90个视觉条目</strong>（总token约361），远低于GPT-5.4（~740）、Claude-Sonnet-4.6（~870）和Gemini-3-Flash（~1100），同时在计数与空间推理任务上取得77.2%的平均分，超越所有对比模型。</p>
<h2 id="二架构与训练-pipeline效率与专项能力的平衡"><strong>二、架构与训练 pipeline：效率与专项能力的平衡</strong></h2>
<h3 id="21-架构设计"><strong>2.1 架构设计</strong></h3>
<p>模型采用类LLaVA的标准架构，以<strong>DeepSeek-V4-Flash</strong>（284B总参数，13B激活参数的MoE模型）为语言骨干，视觉编码器采用自研的<strong>DeepSeek-ViT</strong>，支持任意分辨率输入。</p>
<p><img src="https://picx.zhimg.com/v2-47a5070c223b097feb3534c562fcb921_1440w.jpg" alt=""/></p>
<p><strong>极致压缩是架构的核心</strong>：</p>
<ol>
<li><strong>14×14 Patch Embedding</strong>：将图像切分为基础patch；</li>
<li><strong>3×3空间压缩</strong>：每9个相邻patch在通道维度压缩为1个token；</li>
<li>*Compressed Sparse Attention (CSA)**：在LLM的KV Cache层进一步压缩视觉token。</li>
</ol>
<p>以756×756图像为例：原始571,536像素 → ViT处理为2,916个patch token → 3×3压缩后324个token送入LLM → CSA机制最终仅保留<strong>81个视觉KV条目</strong>。从原始像素到KV Cache，整体压缩比高达<strong>7,056:1</strong>。</p>
<h3 id="22-五阶段后训练流程"><strong>2.2 五阶段后训练流程</strong></h3>
<p>论文设计了一套”先训专家，再合并”的范式：</p>
<ol>
<li><strong>Pretraining</strong>：在数万亿多模态token上预训练，赋予模型输出视觉基元的基础能力；</li>
<li><strong>Specialized SFT</strong>：分别针对Box（FTwG）和Point（FTwP）构建冷启动数据，独立微调，避免模态冲突；</li>
<li><strong>Specialized RL</strong>：对两个专家模型分别应用GRPO强化学习，使用格式、质量、准确率三重Reward Model；</li>
<li><strong>Unified RFT</strong>：用两位专家模型生成拒绝采样数据，统一训练一个融合模型；</li>
<li><strong>On-Policy Distillation</strong>：通过反向KL散度，将专家模型的输出分布蒸馏到统一模型，弥合性能差距。</li>
</ol>
<h2 id="三冷启动数据构造四大推理场景的精细化设计"><strong>三、冷启动数据构造：四大推理场景的精细化设计</strong></h2>
<p>为了让模型学会”用基元思考”，团队没有依赖简单的指令微调，而是为四类任务构建了<strong>带显式视觉锚定的思维链</strong>冷启动数据。</p>
<h3 id="31-计数counting"><strong>3.1 计数（Counting）</strong></h3>
<p>MLLM在密集场景中计数失败，本质是无法建立”语言数字↔视觉实体”的一一对应。</p>
<p><img src="https://pic1.zhimg.com/v2-9900536850a5bc0a2999adbd7794293c_1440w.jpg" alt=""/></p>
<p>包含足球队照片与熊群照片的两个完整推理案例</p>
<ul>
<li><strong>粗粒度计数</strong>：模型先进行意图分析，再<strong>批量 grounding</strong>（同时框出所有候选对象），最后统计求和。Figure 3展示了对团队照片的人数统计，模型一次性框出25个人，再分排验证。</li>
<li><strong>细粒度计数</strong>：基于GQA场景图构造属性约束问题（如”地面上的熊有几只”），模型需<strong>逐一枚举验证</strong>，排除不符合属性的负样本。</li>
</ul>
<h3 id="32-空间推理与通用vqaspatial-reasoning--general-vqa"><strong>3.2 空间推理与通用VQA（Spatial Reasoning &amp; General VQA）</strong></h3>
<p>利用GQA和CLEVR构造数据。在CLEVR合成场景中，模型需要执行<strong>多跳逻辑推理</strong>（如”与灰色金属球同尺寸的紫色橡胶物体是否存在”）。每个推理步骤都必须通过<code class="language-plaintext highlighter-rouge">&lt;|ref|&gt;...&lt;|/ref|&gt;&lt;|box|&gt;...&lt;|/box|&gt;</code>将提及的物体锚定到图像坐标，避免语义漂移。</p>
<p><img src="https://pic1.zhimg.com/v2-060cddf6f583348f8e594baf78da7c52_1440w.jpg" alt=""/></p>
<p>展示CLEVR场景中多属性验证的完整思维链</p>
<h3 id="33-迷宫导航maze-navigation"><strong>3.3 迷宫导航（Maze Navigation）</strong></h3>
<p>这是检验拓扑推理能力的极端场景。纯语言CoT难以描述不规则路径的连通性。</p>
<p>团队使用DFS、Prim、Kruskal算法生成矩形、圆形、六边形三种迷宫拓扑，并构造<strong>不可解迷宫</strong>（在路径中段故意设墙）。模型的思维链以<code class="language-plaintext highlighter-rouge">&lt;|point|&gt;[[x,y]]&lt;|/point|&gt;</code>记录每一步探索坐标，形成类似人类”试错-回溯”的DFS轨迹。</p>
<p><img src="https://pic3.zhimg.com/v2-aab0ba3533a1677b828f4b11e260a30a_1440w.jpg" alt=""/></p>
<p>展示六边形迷宫中从起点到终点的完整探索与回溯过程</p>
<h3 id="34-路径追踪path-tracing"><strong>3.4 路径追踪（Path Tracing）</strong></h3>
<p>在缠绕的贝塞尔曲线中，模型需要追踪指定线条找到终点。难点在于<strong>交叉点消歧</strong>：当两条线相交时，模型必须依据局部几何连续性判断走向，而非依赖颜色捷径。思维链以自适应密度的坐标序列记录路径——直线段稀疏采样，弯曲/交叉处密集采样。</p>
<p><img src="https://pica.zhimg.com/v2-8839b9a3bd04b52043607ddde54dcfc0_1440w.jpg" alt=""/></p>
<p>展示从皇冠图标出发追踪洋红色曲线至终点的过程</p>
<h2 id="四reward-model设计让强化学习看懂视觉推理"><strong>四、Reward Model设计：让强化学习”看懂”视觉推理</strong></h2>
<p>在Specialized RL阶段，论文为不同任务设计了精细的Accuracy RM：</p>
<table>
<thead>
<tr>
<th>任务</th>
<th>Reward Model核心逻辑</th>
</tr>
</thead>
<tbody>
<tr>
<td>计数</td>
<td>基于相对误差的指数衰减奖励：$R = \alpha \cdot \exp(-\beta \cdot \frac{</td>
</tr>
<tr>
<td>空间推理/VQA</td>
<td>LLM-based GRM，分别对思维链和最终回答评分后取平均</td>
</tr>
<tr>
<td>迷宫导航</td>
<td>四维加权：因果探索进度（截断于首次撞墙）、探索完整度（不可解迷宫）、撞墙惩罚、最终路径有效性</td>
</tr>
<tr>
<td>路径追踪</td>
<td>双向轨迹对齐（预测点→真值线 / 真值点→预测线）、端点精度、轨迹连续性惩罚（禁止跳点）</td>
</tr>
</tbody>
</table>
<p><img src="https://pic3.zhimg.com/v2-93a7d536f45713ed9199bdabe65a58d8_1440w.jpg" alt=""/></p>
<p>Table 1的结果极具说服力：</p>
<ul>
<li><strong>计数</strong>：Pixmo-Count达到<strong>89.2%</strong>，超越所有对手；CountQA上RA@10为74.1%，仅次于Gemini-3-Flash；</li>
<li><strong>空间推理</strong>：DS_Spatial_Reasoning达到<strong>98.7%</strong>，显著领先Claude的97.2%和Qwen3-VL的96.8%；</li>
<li><strong>拓扑推理</strong>：这是所有前沿模型的盲区。DS_Maze_Navigation <strong>66.9%</strong>（次高仅50.6%），DS_Path_Tracing <strong>56.7%（次高仅46.5%），形成断层式领先</strong>。</li>
</ul>
<h2 id="五定性分析视觉基元如何重塑推理体验"><strong>五、定性分析：视觉基元如何重塑推理体验</strong></h2>
<p>论文通过大量案例展示，视觉基元不仅是内部推理工具，更外化为可解释的”注意力轨迹”。</p>
<h3 id="51-边界框作为基元"><strong>5.1 边界框作为基元</strong></h3>
<p>模型展现出强大的<strong>涌现协同能力</strong>：</p>
<ul>
<li><strong>世界知识融合</strong>：看到金门大桥照片，模型框出大桥主体，关联到旧金山，进而回答”附近有NBA球队吗”（金州勇士）；</li>
</ul>
<p><img src="https://pica.zhimg.com/v2-8b89936c926c4277676d3a45050e4790_1440w.jpg" alt=""/></p>
<ul>
<li><strong>反事实推理</strong>：在”天平哪边更重”问题中，模型框出左右物体及托盘，通过视觉证据（倾斜角度）推翻外观直觉；</li>
</ul>
<p><img src="https://pic3.zhimg.com/v2-6507bd409ea256eadc80c8b0fc97d5ca_1440w.jpg" alt=""/></p>
<ul>
<li><strong>可操作建议</strong>：在”如何做拿铁”问题中，模型框出咖啡机、蒸汽棒、奶壶、咖啡豆、杯子，给出带空间坐标的操作步骤。</li>
</ul>
<p><img src="https://picx.zhimg.com/v2-cf735f66ba1b8e7122174ef7677f5249_1440w.jpg" alt=""/></p>
<h3 id="52-点作为基元"><strong>5.2 点作为基元</strong></h3>
<p>在迷宫和路径追踪中，模型输出的点序列构成了<strong>可视化的推理路径</strong>。人类可以沿着这些坐标还原模型的”心路历程”：何时尝试分支、何时发现死胡同、何时回溯。这种可解释性是纯语言CoT无法提供的。</p>
<p><img src="https://pic2.zhimg.com/v2-0c0ef35809894886da4aca3e119a14f1_1440w.jpg" alt=""/></p>
<p>圆形迷宫导航与多曲线追踪</p>
<p><code class="language-plaintext highlighter-rouge">https://github.com/deepseek-ai/Thinking-with-Visual-Primitives/blob/main/Thinking_with_Visual_Primitives.pdf</code></p>
<p><br/></p>]]></content><author><name>PaperAgent</name></author><category term="Study"/><category term="training"/><summary type="html"><![CDATA[原文，侵删。]]></summary></entry><entry><title type="html">DeepSeek-V4与流形撕裂(转)</title><link href="https://jinchaoli.com/blog/deepseek-v4%E4%B8%8E%E6%B5%81%E5%BD%A2%E6%92%95%E8%A3%82(%E8%BD%AC)/" rel="alternate" type="text/html" title="DeepSeek-V4与流形撕裂(转)"/><published>2026-04-25T00:00:00+08:00</published><updated>2026-04-25T00:00:00+08:00</updated><id>https://jinchaoli.com/blog/deepseek-v4%E4%B8%8E%E6%B5%81%E5%BD%A2%E6%92%95%E8%A3%82(%E8%BD%AC)</id><content type="html" xml:base="https://jinchaoli.com/blog/deepseek-v4%E4%B8%8E%E6%B5%81%E5%BD%A2%E6%92%95%E8%A3%82(%E8%BD%AC)/"><![CDATA[<h1 id="single-token-geometry-deepseek-v4-and-manifold-tearing"><strong>Single Token Geometry: DeepSeek V4 and Manifold Tearing</strong></h1>
<h3 id="单标几何deepseek-v4-与流形撕裂">单标几何：DeepSeek V4 与流形撕裂</h3>
<p><strong><a href="https://substack.com/@deepmanifold">Deep Manifold</a></strong></p>
<p><strong>Apr 25, 2026</strong></p>
<p>I was preparing <em>Single Token Geometry: Data Complexity,</em> the second entry in this series , when DeepSeek V4 dropped. I set it aside immediately.</p>
<p>What caught my attention wasn’t the benchmark numbers, impressive as they are. It was a paragraph buried in Section 4.2.3 of the technical report:</p>
<blockquote>
<p><em>“We identified that the occurrence of spikes is consistently tied to outliers in the MoE layers, and the routing mechanism itself appears to exacerbate the emergence of these outliers.”</em></p>
</blockquote>
<p>And then, with unusual candor:</p>
<blockquote>
<p><em>“Although a comprehensive theoretical understanding of their underlying mechanisms remains an open question for now, we are sharing them openly to foster further exploration by the community.”</em></p>
</blockquote>
<p>I appreciate DeepSeek’s transparency here. They found three empirical fixes that work. They admitted they don’t fully understand why. And they published anyway. That intellectual honesty is rare, and it’s exactly the kind of opening that geometric thinking is built for.</p>
<p>This post is my attempt to supply part of what they left open: a geometric account of what training instability <em>is</em> in a system like DeepSeek V4, and why their mitigations work.</p>
<p>The thesis is simple and I want to state it plainly upfront:</p>
<p><strong>Loss spikes in MoE training are not optimization failures. They are manifold tears.</strong></p>
<p>我原本正在准备《单标几何：数据复杂性》这个系列的第二篇, 这时 DeepSeek V4 发布了。于是我立刻把原来的文章放到一边。</p>
<p>真正吸引我注意的，并不是那些令人印象深刻的基准测试数字，而是技术报告第 4.2.3 节中埋着的一段话：</p>
<blockquote>
<p>“我们发现，尖峰的出现始终与 MoE 层中的异常值相关，而路由机制本身似乎会加剧这些异常值的产生。”</p>
</blockquote>
<p>然后，他们又以一种少见的坦诚写道：</p>
<blockquote>
<p>“尽管目前我们对这些方法背后的机制尚缺乏完整的理论理解，但我们仍将它们公开分享，以促进社区进一步探索。”</p>
</blockquote>
<p>我非常欣赏 DeepSeek 在这里的透明度。他们找到了三种有效的经验性修复方法。他们承认自己并不完全理解其原因。然后他们仍然选择发表出来。这种智识上的诚实并不常见，而这正是几何思考可以进入的地方。</p>
<p>这篇文章就是我试图补上他们留下的那一部分：为 DeepSeek V4 这样的系统中的训练不稳定性，提供一种几何解释；并说明为什么他们的缓解方法会起作用。</p>
<p>本文的核心论点很简单，我想在开头直接说清楚：</p>
<p><strong>MoE 训练中的损失峰值不是优化失败。它们是流形撕裂。</strong></p>
<hr/>
<h2 id="what-is-a-manifold-tear"><strong>What Is a Manifold Tear?</strong></h2>
<p>Before we can call something a tear, we need to be precise about what a manifold is and what it requires.</p>
<p>A <strong>manifold</strong> is a space that is <em>locally</em> Euclidean. The canonical example is the surface of the Earth: globally curved and non-Euclidean, but zoom in far enough on any point and it looks flat, like a patch of ℝ². More formally, a topological manifold requires that every point has a neighborhood homeomorphic to ℝⁿ. A smooth manifold further requires that the transition maps between overlapping neighborhoods, called <em>charts</em>, are differentiable. A Riemannian manifold additionally equips the space with a metric tensor, giving you notions of distance and curvature.</p>
<p>The key requirement at the foundation of all of this is <strong>continuity</strong>. Not just continuity :smoothness. The whole apparatus of differential geometry, and crucially the whole apparatus of gradient-based optimization, assumes that the space being traversed is smooth enough to support derivatives everywhere.</p>
<p><strong>A manifold tear is a violation of this assumption.</strong> Precisely: a tear occurs when a map between manifold regions fails to be continuous — when a point has no well-defined image, or when nearby points are sent to regions that are not nearby in the target space. A tear is not a large deformation. A large deformation of a manifold is still a manifold. A tear is a <em>discontinuity</em>, the manifold’s local Euclidean structure breaks down at the torn point.</p>
<p>In the context of a transformer’s residual stream, we can define this operationally:</p>
<blockquote>
<p><em>A manifold tear in a transformer’s residual stream is a discontinuity in the layer-to-layer transport map, induced by a discrete routing decision that is inconsistent with the local geometry of the representation manifold at that point.</em></p>
</blockquote>
<p>Now let’s see exactly how that happens in DeepSeek V4 and how the three mitigations each address a different stage of the same failure cascade.</p>
<h2 id="什么是流形撕裂"><strong>什么是流形撕裂？</strong></h2>
<p>在我们把某种现象称为“撕裂”之前，必须先明确：什么是流形？它需要满足什么条件？</p>
<p>流形是一个<strong>局部欧几里得</strong>的空间。最经典的例子是地球表面：从整体看，它是弯曲的、非欧几里得的；但如果你在任意一点足够放大，它看起来就是平坦的, 像一小片 ℝ²。更形式化地说，一个拓扑流形要求每一个点都有一个邻域，并且这个邻域与 ℝ² 同胚。一个光滑流形进一步要求重叠邻域之间的转换映射, 也就是坐标图之间的转换, 是可微的。黎曼流形则在此基础上进一步为空间配备度量张量，从而赋予距离和曲率的概念。</p>
<p>所有这些结构的基础要求，都是<strong>连续性</strong>。不只是连续性，而是光滑性。整个微分几何的装置，尤其是整个基于梯度的优化装置，都默认被穿行的空间足够光滑，因而处处可以支持导数。</p>
<p>流形撕裂就是对这一假设的破坏。更准确地说：当流形区域之间的某个映射不再连续时，撕裂就发生, 也就是说，某个点没有良好定义的像，或者彼此相近的点被送到了目标空间中并不相近的区域。撕裂不是大的形变。一个流形即使发生很大的形变，仍然可以是流形。撕裂是不连续性, 在被撕裂的点上，流形的局部欧几里得结构失效了。</p>
<p>在 Transformer 的残差流语境中，我们可以操作性地定义它：</p>
<blockquote>
<p>Transformer 残差流中的流形撕裂，是层与层之间传输映射中的一种不连续性；这种不连续性由离散路由决策诱发，而该路由决策与该点处表征流形的局部几何不一致。</p>
</blockquote>
<p>现在，让我们具体看看这一点在 DeepSeek V4 中是如何发生的, 以及它的三种缓解方法，如何分别处理同一个失效级联中的不同阶段。</p>
<hr/>
<h2 id="the-geometry-of-the-residual-stream"><strong>The Geometry of the Residual Stream</strong></h2>
<p>Think of a single token passing through a transformer. At each layer, its hidden state is a point in ℝᵈ , a high-dimensional vector. As the token passes through successive layers, this point traces a trajectory through representation space. The claim that this trajectory lives on a manifold is implicit in how we train: gradient descent assumes a smooth loss landscape, backpropagation assumes differentiable operations everywhere, and the expressive power of deep networks comes precisely from learning smooth, structured transformations of this space.</p>
<p>In a standard transformer, every token passes through the same FFN at each layer. The map from layer <em>l</em> to layer <em>l+1</em> is the same for all tokens. The manifold deforms, but it deforms <em>continuously</em> — the same function applied everywhere.</p>
<p>In a <strong>Mixture-of-Experts</strong> (MoE) transformer like DeepSeek V4, this changes fundamentally. At each MoE layer, a router examines the token’s hidden state and routes it to one or more experts, a discrete, Top-k selection from hundreds of possible FFNs. The composite map from hidden state to new hidden state is now:</p>
<blockquote>
<p>hidden state → routing decision → expert application → new hidden state</p>
</blockquote>
<p>The routing decision is <strong>discontinuous by construction</strong>. It is a discrete selector. A token sitting at position x in representation space gets routed to expert E₁. A token at position x + ε , infinitesimally close, might get routed to expert E₂. These two experts were trained on different regions of the manifold. Their outputs are not guaranteed to be close to each other.</p>
<p>This means the layer-to-layer transport map has <strong>discontinuities at routing boundaries</strong>. The manifold is already torn, structurally, at every expert boundary. This is not a bug — it is the price of the MoE architecture’s efficiency. But it is a latent geometric fragility that becomes catastrophic under the right conditions.</p>
<h2 id="残差流的几何"><strong>残差流的几何</strong></h2>
<p>想象一个单标正在穿过一个 Transformer。在每一层，它的隐藏状态都是 ℝᵈ中的一个点: 一个高维向量。随着这个单标穿过连续的层，这个点就在表征空间中划出一条轨迹。我们说这条轨迹生活在某个流形上，这一点其实隐含在训练方式之中：梯度下降假设损失景观是光滑的；反向传播假设所有操作处处可微；而深度网络的表达能力，恰恰来自于它能够学习这个空间上的光滑、有结构的变换。</p>
<p>在标准 Transformer 中，每个单标在每一层都会经过同一个 FFN。从第 <em>l</em> 层到第 <em>l+1</em> 层的映射，对所有单标来说都是同一个映射。流形会形变，但它是连续形变, 同一个函数作用在整个空间上。</p>
<p>但在像 DeepSeek V4 这样的 Mixture-of-Experts（MoE）Transformer 中，情况发生了根本变化。在每一个 MoE 层，路由器会检查单标的隐藏状态，并将它路由到一个或多个专家, 也就是从数百个可能的 FFN 中进行一次离散的 Top-k 选择。于是，从隐藏状态到新隐藏状态的复合映射变成了：</p>
<blockquote>
<p><em>隐藏状态  →  路由决策  →  专家作用  →  新的隐藏状态</em></p>
</blockquote>
<p>路由决策在构造上就是不连续的。它是一个离散选择器。位于表征空间中某一点 x 的单标，可能被路由到专家 x + ε。而位于 E₁ 的另一个单标——哪怕它与 x 无限接近——也可能被路由到专家 E₂。这两个专家是在流形的不同区域上训练出来的。它们的输出并不保证彼此接近。</p>
<p>这意味着，层与层之间的传输映射在路由边界处存在不连续性。从结构上说，流形在每一个专家边界处都已经存在撕裂的可能。这并不是一个 bug，而是 MoE 架构效率所付出的代价。但它是一种潜在的几何脆弱性；在合适的条件下，这种脆弱性会变成灾难性的训练不稳定。</p>
<hr/>
<h2 id="the-failure-cascade"><strong>The Failure Cascade</strong></h2>
<p>The paper’s observation, that spikes are tied to outliers in MoE layers, and that routing exacerbates them, describes a specific failure cascade. Geometrically, it unfolds in three stages:</p>
<p><strong>Stage 1: Local curvature spike.</strong> An activation in an MoE expert grows very large. The SwiGLU gate or linear component produces an extreme value. This is not yet a tear — it is a region of very high curvature on the representation manifold. The local geometry is still technically defined, but it is poorly conditioned. Small changes in input produce large changes in output. The optimizer’s gradient step, calibrated for normal curvature, begins to overshoot.</p>
<p><strong>Stage 2: Chart inconsistency.</strong> The router, updating synchronously with the backbone, now operates on a shifted manifold. At step t, the backbone parameters θₜ define a representation space Mₜ. But the router, which just updated, is making routing decisions as if the manifold were Mₜ₋₁. A token at position x on Mₜ gets routed as if it were on a manifold that no longer exists. This is a <strong>chart inconsistency</strong>: the coordinate chart (the routing assignment) no longer matches the local geometry of the point being mapped. The token is being sent to the wrong expert for where it actually lives in the representation space.</p>
<p><strong>Stage 3: Tear amplification.</strong> The misrouted token enters an expert that wasn’t trained on its region of the manifold. The expert produces an outlier output — a point far from where the token should be in the next layer’s representation space. This discontinuous jump is the tear. And if the residual mapping matrices are expansive (spectral norm &gt; 1), the tear grows as it propagates through subsequent layers. By layer L, a token that was slightly misrouted has been thrown far from its correct manifold region. The loss spike is the accumulated geometric damage becoming visible in the training objective.</p>
<p>This is the cascade: <strong>high curvature → chart inconsistency → routing boundary discontinuity → tear amplification → loss spike.</strong></p>
<p>DeepSeek V4’s three mitigations each interrupt this cascade at a different stage.</p>
<h2 id="失效级联"><strong>失效级联</strong></h2>
<p>论文中的观察 残差尖峰与 MoE 层中的异常值相关，而路由机制会加剧这些异常值——描述的是一个具体的失效级联。从几何上看，它分为三个阶段：</p>
<p><strong>阶段一：局部曲率尖峰。</strong>MoE 专家中的某个激活值变得非常大。SwiGLU 的门控项或线性项产生了一个极端值。这还不是撕裂: 它是表征流形上一块曲率极高的区域。局部几何在技术上仍然是有定义的，但条件已经很差。输入中的微小变化，会导致输出中的巨大变化。原本按照正常曲率校准的优化器梯度步长，开始发生过冲。</p>
<p><strong>阶段二：坐标图不一致。</strong>路由器与主干网络同步更新，于是它现在作用在一个已经发生位移的流形上。在第 t步，主干参数 θₜ 定义了一个表征空间 Mₜ。但刚刚更新过的路由器，却像是在流形 Mₜ₋₁ 上做路由决策。位于 Mₜ上某一点 x 的单标，被当作仍位于一个已经不存在的流形上来路由。这就是坐标图不一致：坐标图，也就是路由分配，已经不再匹配该点被映射时的局部几何。这个单标被送到了一个并不适合它当前表征位置的专家那里。</p>
<p><strong>阶段三：撕裂放大。</strong>被错误路由的单标进入了一个并不是在它所在流形区域上训练出来的专家。这个专家产生了一个异常输出, 也就是一个远离该单标在下一层表征空间中本应到达位置的点。这个不连续跳跃，就是撕裂。而如果残差映射矩阵是扩张性的，也就是谱范数大于 1，那么这种撕裂会在后续层传播时不断放大。到第 L层时，一个原本只是轻微错路由的单标，已经被抛离了它正确的流形区域。残差尖峰，就是这种累积的几何损伤在训练目标中的显现。</p>
<p>这就是整个级联：</p>
<p>高曲率  →  坐标图不一致  →  路由边界不连续  →  撕裂放大  →  残差尖峰</p>
<p>DeepSeek V4 的三种缓解方法，正是在这个级联的不同阶段将其打断。</p>
<hr/>
<h2 id="mitigation-1-swiglu-clamping-bounding-local-curvature"><strong>Mitigation 1: SwiGLU Clamping, Bounding Local Curvature</strong></h2>
<p>This is the earliest intervention, attacking Stage 1 before the cascade begins.</p>
<p>The SwiGLU activation computes:</p>
<blockquote>
<p>SwiGLU(x, g) = x · σ(g) · g</p>
</blockquote>
<p>When either the linear component x or the gate g becomes very large, the local curvature of the loss surface spikes. Mathematically, curvature is related to the second derivative of the map — and when activations are extreme, second derivatives become extreme. The optimizer’s gradient step assumes the loss landscape is locally well-approximated by its first-order Taylor expansion. High curvature violates this assumption. The step overshoots. The next gradient is wildly miscalibrated. The cascade is initiated.</p>
<p>DeepSeek V4 clamps the linear component to <strong>[−10, 10]</strong> and caps the gate at <strong>10</strong>. Geometrically, this is a <strong>curvature bound</strong>: it enforces that no single point in the activation space can develop extreme local curvature. The manifold remains smooth enough at every point that gradient steps stay valid.</p>
<p>Think of it as a <strong>chart boundary condition</strong>. A chart in differential geometry is only valid within a bounded region — you cannot use a single flat map to cover the entire Earth. SwiGLU clamping is enforcing that activations stay within the region where the local chart (the linear approximation used by the optimizer) remains valid. Step outside that region, and the chart breaks down. The clamp keeps you inside.</p>
<p>The paper notes this works without compromising performance — which makes geometric sense. Clamping doesn’t restrict the manifold’s expressivity; it restricts the <em>curvature</em> of any single point. The manifold can still be highly complex and nonlinear globally. It just can’t have singular points locally.</p>
<h2 id="缓解方法一swiglu-clamping-约束局部曲率"><strong>缓解方法一：SwiGLU Clamping, 约束局部曲率</strong></h2>
<p>这是最早发生作用的干预方式，它在级联开始之前就攻击了阶段一。</p>
<p>SwiGLU 激活计算的是：</p>
<blockquote>
<p>SwiGLU(x, g) = x · σ(g) · g</p>
</blockquote>
<p>当线性项 xxx 或门控项 ggg 变得非常大时，损失曲面的局部曲率就会出现尖峰。从数学上说，曲率与映射的二阶导数相关——而当激活值变得极端时，二阶导数也会变得极端。优化器的梯度步，默认损失景观在局部可以被一阶泰勒展开很好地近似。高曲率破坏了这一假设。于是梯度步发生过冲。下一步的梯度被严重误校准。整个失效级联由此被启动。</p>
<p>DeepSeek V4 将线性项限制在 <strong>[−10, 10] </strong>范围内，并将门控项的上界限制为 10。从几何上看，这就是一种<strong>曲率约束</strong>：它强制激活空间中的任何单个点都不能发展出极端的局部曲率。于是，流形在每个点上都保持足够光滑，使梯度步仍然有效。</p>
<p>可以把它理解为一种<strong>坐标图边界条件</strong>。在微分几何中，一个坐标图只在某个有界区域内有效——你不能用一张平面地图覆盖整个地球。SwiGLU clamping 强制激活值停留在这样一个区域内：在这个区域中，优化器所使用的局部坐标图，也就是线性近似，仍然有效。一旦走出这个区域，坐标图就会失效。clamp 的作用，就是把你留在这个区域之内。</p>
<p>论文指出，这种方法不会损害模型性能——这在几何上是说得通的。Clamping 并不限制流形的表达能力；它限制的是任何单个点的曲率。流形在全局上仍然可以高度复杂、强非线性。它只是不能在局部产生奇异点。</p>
<hr/>
<h2 id="mitigation-2-anticipatory-routing-preventing-chart-inconsistency"><strong>Mitigation 2: Anticipatory Routing, Preventing Chart Inconsistency</strong></h2>
<p>Where SwiGLU clamping addresses the precondition, Anticipatory Routing attacks Stage 2 directly: the moment of chart inconsistency.</p>
<p>Recall the problem: at training step t, the backbone parameters θₜ define the current representation manifold Mₜ. If the router also updates to θₜ simultaneously, it is now making routing decisions based on a manifold that is <em>being constructed in real time</em>. The routing chart and the geometric chart are out of sync.</p>
<p>Anticipatory Routing enforces a <strong>temporal consistency condition</strong>: at step t, routing decisions are made using the historical parameters θₜ₋Δₜ. The router operates on the snapshot of the manifold that produced the current token representations. In differential geometry terms, this is analogous to a <strong>connection</strong> — a rule for parallel-transporting objects along the manifold in a consistent way. You don’t route a vector using the geometry at the destination; you route it using the geometry at the source.</p>
<p>The implementation is elegant: the data for step t is fetched in advance at step t − Δt, and routing indices are precomputed and cached. The router never sees the manifold mid-update. The discrete chart (routing assignment) and the continuous chart (representation geometry) remain synchronized.</p>
<p>The dynamic activation is particularly revealing. The system detects loss spikes and <em>then</em> activates Anticipatory Routing for a stabilization period before reverting to standard training. This is the system monitoring for chart inconsistency and applying the consistency condition on demand — a feedback loop that treats geometric misalignment as a detectable, correctable event rather than an inevitable one.</p>
<h2 id="缓解方法二anticipatory-routing-防止坐标图不一致"><strong>缓解方法二：Anticipatory Routing, 防止坐标图不一致</strong></h2>
<p>如果说 SwiGLU clamping 处理的是失效级联的前提条件，那么 Anticipatory Routing 直接攻击的就是阶段二：坐标图不一致发生的那个瞬间。</p>
<p>回忆一下问题所在：在训练第 t 步，主干网络参数 θₜ₋Δₜ 定义了当前的表征流形 Mₜ。如果路由器也同时更新到 θₜ₋Δₜ，那么它就在一个正在实时构造的流形上做路由决策。路由坐标图与几何坐标图因此发生了不同步。</p>
<p>Anticipatory Routing 强制引入一种<strong>时间一致性条件</strong>：在第 t 步，路由决策使用历史参数 θₜ₋Δₜ 来完成。也就是说，路由器作用在产生当前单标表征的那个流形快照上。用微分几何的话说，这类似于一种<strong>联络</strong>, 一种沿着流形一致地平行移动对象的规则。你不应该用目的地处的几何来路由一个向量；你应该用源点处的几何来路由它。</p>
<p>它的实现很优雅：第 t 步的数据会在第 θₜ₋Δₜ 步提前取出，路由索引也会被预先计算并缓存。这样，路由器永远不会看到一个正在更新中的流形。离散坐标图，也就是路由分配；以及连续坐标图，也就是表征几何，因而保持同步。</p>
<p>它的动态激活机制尤其值得注意。系统会检测残差尖峰，然后在一段稳定化期间启用 Anticipatory Routing，之后再回到标准训练。这等于系统在监测坐标图不一致，并按需施加一致性条件, 这是一种反馈回路，它把几何错位视为一种可检测、可修正的事件，而不是不可避免的宿命。</p>
<hr/>
<h2 id="mitigation-3-mhc--bounding-tear-propagation"><strong>Mitigation 3: mHC — Bounding Tear Propagation</strong></h2>
<p>The Manifold-Constrained Hyper-Connections (mHC) is the deepest of the three interventions, and the one most explicitly geometric in the paper’s own framing , it’s right there in the name.</p>
<p>Standard Hyper-Connections expand the residual stream width by a factor of n_hc, introducing a residual mapping matrix B_l ∈ ℝⁿ×ⁿ at each layer. The update rule is:</p>
<blockquote>
<p>(X_{l+1} = B_l X_l + C_l F_l(A_l X_l))</p>
</blockquote>
<p>The paper found that naive HC exhibited numerical instability when stacked, precisely because B_l is unconstrained. An unconstrained matrix can have spectral norm &gt; 1. Spectral norm is the largest singular value, it is the maximum factor by which the matrix can stretch a vector. If ‖B_l‖₂ &gt; 1, the residual mapping is <strong>expansive</strong>: it stretches the representation space at each layer. A small perturbation, say, a token slightly misrouted at layer 5:gets amplified at layer 6, further amplified at layer 7, and so on. By the time it reaches layer L, the tear has been stretched across a large region of representation space. This is tear amplification.</p>
<p>The <strong>Birkhoff polytope constraint</strong> fixes this by requiring B_l to be a doubly stochastic matrix:</p>
<blockquote>
<table>
<tbody>
<tr>
<td>B_l ∈ M := {M ∈ ℝⁿ×ⁿ</td>
<td>M·1ₙ = 1ₙ, 1ₙᵀ·M = 1ₙᵀ, M ≥ 0}</td>
</tr>
</tbody>
</table>
</blockquote>
<p>A doubly stochastic matrix has spectral norm bounded by 1. This makes B_l <strong>non-expansive</strong>: for any two hidden states x and y,</p>
<blockquote>
<p>‖B_l·x − B_l·y‖ ≤ ‖x − y‖</p>
</blockquote>
<p>The residual mapping cannot increase the distance between any two points. It is a <strong>Lipschitz-1 map on the residual stream</strong>. A tear introduced at any layer cannot grow as it propagates through subsequent layers. The manifold can be torn, mHC does not remove the routing boundary discontinuities, but it cannot be <em>stretched apart</em>. The damage is contained.</p>
<p>The Sinkhorn-Knopp algorithm that enforces this constraint is itself geometrically beautiful: it performs alternating projections onto two convex constraint sets, row-normalized matrices, and column-normalized matrices, until their intersection (the Birkhoff polytope) is reached. You are iteratively projecting B_l onto the manifold of doubly stochastic matrices. The constraint manifold M is itself a well-studied convex polytope, and Sinkhorn-Knopp is a convergent algorithm for finding the nearest point on it.</p>
<p>Additionally, the input and output mappings A_l and C_l are constrained to be non-negative and bounded via Sigmoid functions. This prevents signal cancellation, another form of geometric pathology where opposing contributions annihilate each other, creating artificial zeros in the representation space that have no geometric meaning.</p>
<h2 id="缓解方法三mhc约束撕裂传播"><strong>缓解方法三：mHC——约束撕裂传播</strong></h2>
<p>Manifold-Constrained Hyper-Connections（mHC，流形约束超连接）是三种干预中最深层的一种，也是论文自身表述中最明确具有几何含义的一种, 这一点已经直接写在名字里了。</p>
<p>标准 Hyper-Connections 会将残差流的宽度扩展 B_l 倍，并在每一层引入一个残差映射矩阵 B_l ∈ ℝⁿ×ⁿ。其更新规则为：</p>
<blockquote>
<p>(X_{l+1} = B_l X_l + C_l F_l(A_l X_l))</p>
</blockquote>
<p>论文发现，朴素的 HC 在堆叠多层时会表现出数值不稳定性——原因正是 BlB_lBl 没有受到约束。一个无约束矩阵的谱范数可能大于 1。谱范数是最大奇异值，也就是这个矩阵能够拉伸一个向量的最大倍数。如果 ‖B_l‖₂ &gt; 1，那么残差映射就是扩张性的：它会在每一层拉伸表征空间。一个很小的扰动, 比如某个单标在第 5 层被轻微错误路由, 会在第 6 层被放大，在第 7 层进一步放大，如此继续。等它到达第 L 层时，这个撕裂已经被拉伸到表征空间中的一大片区域。这就是<strong>撕裂放大</strong>。</p>
<p>Birkhoff 多面体约束通过要求 B_l 成为一个双随机矩阵来解决这个问题：</p>
<blockquote>
<table>
<tbody>
<tr>
<td>B_l ∈ M := {M ∈ ℝⁿ×ⁿ</td>
<td>M·1ₙ = 1ₙ, 1ₙᵀ·M = 1ₙᵀ, M ≥ 0}</td>
</tr>
</tbody>
</table>
</blockquote>
<p>一个双随机矩阵的谱范数被 1 所约束。这使得 BlB_lBl 成为非扩张映射：对任意两个隐藏状态 xxx 和 yyy，都有</p>
<blockquote>
<p>‖B_l·x − B_l·y‖ ≤ ‖x − y‖</p>
</blockquote>
<p>也就是说，残差映射不能增加任意两点之间的距离。它是残差流上的一个 <strong>Lipschitz-1 映射</strong>。任何一层中引入的撕裂，都不能在后续层传播时继续增大。流形仍然可能被撕裂, mHC 并没有消除路由边界处的不连续性, 但它不能被进一步拉开。损伤被限制住了。</p>
<p>执行这一约束的 Sinkhorn-Knopp 算法本身也具有很漂亮的几何意义：它在两个凸约束集合之间进行交替投影, 行归一化矩阵集合与列归一化矩阵集合, 直到到达它们的交集，也就是 Birkhoff 多面体。换句话说，你是在迭代地将 B_l 投影到双随机矩阵的流形上。这个约束流形 M 本身是一个研究充分的凸多面体，而 Sinkhorn-Knopp 是一种用于找到其上相应约束点的收敛算法。</p>
<p>此外，输入映射 A_l 和输出映射 C_l 也通过 Sigmoid 函数被约束为非负且有界。这可以防止信号抵消, 另一种几何病态：相反方向的贡献彼此湮灭，在表征空间中制造出没有真实几何意义的人为空零点.</p>
<hr/>
<h2 id="the-unified-picture"><strong>The Unified Picture</strong></h2>
<p>The three mitigations form a layered geometric defense, each acting at a different stage of the failure cascade:</p>
<p><strong>Stage 1.</strong></p>
<ul>
<li><strong>Geometric Event: </strong>High local curvature</li>
<li><strong>Mitigation: </strong>SwiGLU Clamping</li>
<li><strong>Mechanism : </strong>Bounds activation magnitude; keeps optimizer’s local chart valid</li>
</ul>
<p><strong>Stage 2.</strong></p>
<ul>
<li><strong>Geometric Event: </strong>Chart inconsistency</li>
<li><strong>Mitigation: </strong>Anticipatory Routing</li>
<li><strong>Mechanism : </strong>Synchronizes routing chart with geometric chart via temporal consistency</li>
</ul>
<p><strong>Stage 3.</strong></p>
<ul>
<li><strong>Geometric Event: </strong>Tear amplification</li>
<li><strong>Mitigation: </strong>mHC (Birkhoff)</li>
<li><strong>Mechanism : </strong>Non-expansive residual mapping; Lipschitz-1 bound across layers</li>
</ul>
<p>None of the three individually suffices. SwiGLU clamping reduces the probability of a high-curvature precondition but cannot prevent all routing misalignment. Anticipatory Routing prevents chart inconsistency during normal training but is only activated reactively. mHC contains the damage if a tear occurs but does not prevent it from forming.</p>
<p>Together, they form a cascade interrupt: clamping attacks the precondition, routing consistency attacks the event, and Lipschitz bounding attacks the aftermath. This is why the paper could say, with some confidence, that training was stabilized — even without a complete theoretical account.</p>
<h2 id="统一图景"><strong>统一图景</strong></h2>
<p>这三种缓解方法构成了一套分层的几何防御体系，分别作用于失效级联的不同阶段：</p>
<p><strong>阶段 1.</strong></p>
<ul>
<li><strong>几何事件: </strong>高局部曲率</li>
<li><strong>解方法: </strong>SwiGLU Clamping</li>
<li><strong>机制 : </strong>约束激活幅度；保持优化器的局部坐标图有效</li>
</ul>
<p><strong>阶段 2.</strong></p>
<ul>
<li><strong>几何事件: </strong>坐标图不一致</li>
<li><strong>解方法: </strong>Anticipatory Routing</li>
<li><strong>机制: </strong>通过时间一致性，使路由坐标图与几何坐标图同步</li>
</ul>
<p><strong>阶段 3.</strong></p>
<ul>
<li><strong>几何事件: </strong>撕裂放大</li>
<li><strong>解方法: </strong>mHC (Birkhoff)</li>
<li><strong>机制: </strong>非扩张残差映射；在层间施加 Lipschitz-1 约束</li>
</ul>
<p>这三者中的任何一个，单独来看都不足够。SwiGLU clamping 降低了高曲率前提出现的概率，但不能阻止所有路由错位。Anticipatory Routing 可以在正常训练中防止坐标图不一致，但它是反应式激活的。mHC 可以在撕裂发生之后限制损伤，但并不能阻止撕裂本身的形成。</p>
<p>三者合在一起，构成了一种<strong>级联中断机制</strong>：clamping 攻击前提条件，路由一致性攻击事件本身，而 Lipschitz 约束攻击事后的传播放大。这也解释了为什么论文可以相当有信心地说，训练被稳定住了, 即使他们还没有给出一个完整的理论解释。</p>
<hr/>
<h2 id="what-the-paper-leaves-open"><strong>What the Paper Leaves Open</strong></h2>
<p>The authors write:</p>
<blockquote>
<p><em>“Although a comprehensive theoretical understanding of their underlying mechanisms remains an open question for now…”</em></p>
</blockquote>
<p>The geometric framing offered here suggests what that theoretical understanding might look like: a formal account of how routing boundary discontinuities propagate through residual streams, how spectral norm bounds on residual mappings contain this propagation, and how temporal consistency conditions in discrete selectors can be enforced as a parallel transport rule.</p>
<p>This is not a complete theory. It is a vocabulary, a set of geometric concepts precise enough to ask the right questions. The manifold is torn by construction in every MoE layer. The question is whether that tear propagates catastrophically or stays bounded. DeepSeek V4’s three mitigations are three answers to that question, each operating at a different scale.</p>
<p>Single Token Geometry is the project of taking seriously the idea that what happens to one token, passing through one forward pass, is already geometrically rich enough to explain phenomena we currently attribute to optimization dynamics. Loss spikes are one example.</p>
<h2 id="论文留下了什么开放问题"><strong>论文留下了什么开放问题</strong></h2>
<p>作者写道：</p>
<blockquote>
<p><em>“</em>“尽管目前我们对这些方法背后的机制尚缺乏完整的理论理解……”</p>
</blockquote>
<p>这里提出的几何框架，提示了这种理论理解可能是什么样子：它可以是一种形式化解释，用来说明路由边界不连续性如何沿着残差流传播，残差映射上的谱范数约束如何限制这种传播，以及离散选择器中的时间一致性条件如何被理解为一种平行移动规则。</p>
<p>这还不是一个完整理论。它是一套词汇, 一组足够精确的几何概念，使我们能够提出正确的问题。每一个 MoE 层在结构上都会撕裂流形。真正的问题是：这种撕裂会灾难性地传播，还是会被约束在有界范围内？DeepSeek V4 的三种缓解方法，正是对这个问题的三种回答，并且分别作用在不同尺度上。</p>
<p><strong>单标几何</strong>这个项目，就是认真对待这样一个想法：一个单标在一次前向传播中所经历的过程，本身就已经具有足够丰富的几何结构，足以解释许多我们目前归因于优化动力学的现象。残差尖峰只是其中一个例子。</p>
<hr/>
<h3 id="manifold-tearing-is-not-deepseeks-problem-alone"><strong>Manifold Tearing Is Not DeepSeek’s Problem Alone</strong></h3>
<p>It is worth being precise about scope. Manifold tearing is not unique to DeepSeek, or to MoE architectures — though MoE does make it worse, by introducing routing boundaries that are discontinuous by construction. The pathology is more general.</p>
<p>We noticed and discussed this in our 2024 paper, <em>Deep Manifold Part 1: Anatomy of Neural Network Manifold</em> — specifically Section 3.6, Learning Transformation:</p>
<blockquote>
<p><em>“This explains the zig-zag pattern observed in the loss curve during the slow decline stage of almost all foundation model training, suggesting that training is struggling to converge — a hidden bottleneck identified in our analysis. A model is considered to have converged effectively when the standard deviation of its loss values stabilizes at less than 5.”</em></p>
</blockquote>
<p>That zig-zag pattern is not noise. It is not a quirk of the optimizer or the learning rate schedule. High loss deviation is an indication of manifold tearing — the loss surface is not smooth but discontinuous, and the optimizer is crossing those discontinuities rather than descending through them. The standard deviation of the loss is, in this framing, a roughness measure of the representation manifold. When it stabilizes, the manifold has settled into a geometry the optimizer can navigate. When it spikes, the manifold is tearing.</p>
<p>This means manifold tearing is visible in almost every foundation model training run ever published. It has been attributed to many things — learning rate warmup, batch size scheduling, data curriculum, optimizer hyperparameters. These attributions are not wrong. But they are proximate causes. They describe the conditions under which tearing is more or less likely. They do not identify the root cause.</p>
<p>The root cause, beyond model architecture, is <strong>data</strong>.</p>
<p>Specifically: data complexity. The representation manifold that a model learns is not chosen by the architect , it is <em>induced</em> by the training data. A dataset with discontinuous structure,sharp distributional boundaries between domains, conflicting label geometries, or extreme token frequency imbalances — induces a representation manifold with discontinuities baked in from the first forward pass. The model is not tearing a smooth manifold. It is trying to learn a manifold that was never smooth to begin with. The routing boundaries in MoE are a second-order effect; the data boundaries are primary.</p>
<p>This is the argument of the next article in this series.</p>
<p><em>Single Token Geometry: Data Complexity</em> will ask what it means, geometrically, for data to be complicit in manifold tearing: why certain data compositions make smooth representation manifolds impossible, and what that implies for how we should think about dataset curation, not as an engineering convenience, but as a geometric necessity.</p>
<p>Manifold tearing will continue to appear throughout this series. DeepSeek V4 gave us a precise, honest, and unusually well-documented instance of it. The mitigations they found are real and they work. But they are defenses against a pathology whose origin sits upstream of the architecture — in the data the model is asked to learn from, and in the geometry that data imposes on the representation space before training even begins.</p>
<h2 id="流形撕裂并不只是-deepseek-的问题"><strong>流形撕裂并不只是 DeepSeek 的问题</strong></h2>
<p>有必要准确界定一下范围。流形撕裂并不是 DeepSeek 独有的问题，也并不是 MoE 架构独有的问题——尽管 MoE 通过引入构造上不连续的路由边界，确实会让这个问题更加严重。这个病态现象其实更一般。</p>
<p>我们在 2024 年的论文《Deep Manifold Part 1: Anatomy of Neural Network Manifold》中已经注意并讨论过这一点，尤其是在第 3.6 节“Learning Transformation”中：</p>
<blockquote>
<p>这解释了几乎所有基础模型训练在缓慢下降阶段的残差曲线中所观察到的锯齿形模式，表明训练正在艰难收敛——这是我们分析中识别出的一个隐藏瓶颈。当残差值的标准差稳定在小于 5 时，可以认为模型已经有效收敛。</p>
</blockquote>
<p>这种锯齿形模式不是噪声。它不是优化器或学习率调度的某种偶然现象。高残差偏差是流形撕裂的迹象, 损失曲面不是光滑的，而是不连续的；优化器不是沿着它下降，而是在穿越这些不连续处。在这个框架中，残差的标准差可以被看作表征流形的一个粗糙度度量。当它稳定下来时，说明流形已经沉降到一种优化器可以导航的几何之中。当它出现尖峰时，说明流形正在撕裂。</p>
<p>这意味着，流形撕裂几乎可以在每一次公开发表的基础模型训练过程中看到。它过去被归因于很多事情, 学习率 warmup、batch size 调度、数据课程、优化器超参数。这些归因并不是错的。但它们只是近因。它们描述的是在什么条件下撕裂更容易或更不容易发生，却没有指出根本原因。</p>
<p>架构之外的根本原因，是数据。</p>
<p>更具体地说：是数据复杂性。模型所学习到的表征流形，并不是由架构师自由选择的,它是由训练数据诱导出来的。一个具有不连续结构的数据集,例如领域之间存在尖锐的分布边界、标签几何彼此冲突，或者单标频率存在极端不平衡, 会从第一次前向传播开始，就诱导出一个内部带有不连续性的表征流形。模型并不是在撕裂一个原本光滑的流形。它是在试图学习一个从一开始就并不光滑的流形。MoE 中的路由边界是二阶效应；数据边界才是一阶原因。</p>
<p>这正是本系列下一篇文章要讨论的问题。</p>
<p>《单标几何：数据复杂性》将追问：从几何上看，数据如何“<strong>共谋</strong>”参与流形撕裂？为什么某些数据组合会让光滑的表征流形变得不可能？这又意味着我们应该如何重新理解数据集清洗与构造, 它不只是工程上的便利，而是一种几何上的必要。</p>
<p>流形撕裂将会在这个系列中反复出现。DeepSeek V4 给了我们一个精确、诚实、而且罕见地记录充分的案例。他们找到的缓解方法是真实有效的。但这些方法所防御的病态，其源头位于架构之前, 位于模型被要求学习的数据之中，也位于这些数据在训练开始之前就施加到表征空间上的几何之中。</p>
<p>###</p>]]></content><author><name>Deep Manifold</name></author><category term="Study"/><category term="training"/><category term="moe"/><summary type="html"><![CDATA[原文，侵删。]]></summary></entry><entry><title type="html">Python基础</title><link href="https://jinchaoli.com/blog/python%E5%9F%BA%E7%A1%80/" rel="alternate" type="text/html" title="Python基础"/><published>2026-04-24T00:00:00+08:00</published><updated>2026-04-24T00:00:00+08:00</updated><id>https://jinchaoli.com/blog/python%E5%9F%BA%E7%A1%80</id><content type="html" xml:base="https://jinchaoli.com/blog/python%E5%9F%BA%E7%A1%80/"><![CDATA[<h2 id="lists">Lists</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
</pre></td><td class="rouge-code"><pre><span class="n">arr</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>

<span class="c1"># Common Operations
</span><span class="n">arr</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>      <span class="c1"># Find index
</span><span class="n">arr</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>     <span class="c1"># Add to end
</span><span class="n">arr</span><span class="p">.</span><span class="nf">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># Add 10 from left (at index 0 which is start)
</span><span class="n">arr</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>     <span class="c1"># Remove value
</span><span class="n">arr</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>         <span class="c1"># Remove &amp; return last element
</span><span class="n">arr</span><span class="p">.</span><span class="nf">sort</span><span class="p">()</span>        <span class="c1"># In-place sort (TimSort: O(n log n))
</span><span class="n">arr</span><span class="p">.</span><span class="nf">sort</span><span class="p">(</span><span class="n">reverse</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>  <span class="c1"># In-place reverse (High to low)
</span><span class="n">arr</span><span class="p">.</span><span class="nf">reverse</span><span class="p">()</span>     <span class="c1"># In-place reverse
</span><span class="n">arr</span><span class="p">.</span><span class="nf">copy</span><span class="p">()</span>        <span class="c1"># Return shallow copy
</span>
<span class="c1"># List Slicing
</span><span class="n">arr</span><span class="p">[</span><span class="n">start</span><span class="p">:</span><span class="n">stop</span><span class="p">:</span><span class="n">step</span><span class="p">]</span>  <span class="c1"># Generic slice syntax
</span><span class="n">arr</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>    <span class="c1"># Last item
</span><span class="n">arr</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># Reverse list
</span><span class="n">arr</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>    <span class="c1"># Everything after index 1
</span><span class="n">arr</span><span class="p">[:</span><span class="mi">3</span><span class="p">]</span>    <span class="c1"># First three elements
</span>
<span class="c1"># Sublists (aka slicing), 左闭右开
</span><span class="n">arr</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="mi">2</span><span class="p">]</span>   <span class="c1"># [2]
# Similar to for-loop ranges, last index is non-inclusive
# But no out of bounds error
</span><span class="n">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">10</span><span class="p">]</span>  <span class="c1"># [1, 2, 3]
</span>
<span class="c1"># Custom sort (e.g., by length of string)
</span><span class="n">arr</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">bob</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">alice</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">jane</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">doe</span><span class="sh">"</span><span class="p">]</span>
<span class="n">arr</span><span class="p">.</span><span class="nf">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="nf">len</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
<span class="nf">print</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>  <span class="c1"># ['bob', 'doe', 'jane', 'alice']
</span>
<span class="c1"># 2-D lists
</span><span class="n">arr</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="mi">4</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span>
<span class="nf">print</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="n">arr</span><span class="p">[</span><span class="mi">3</span><span class="p">][</span><span class="mi">3</span><span class="p">])</span>

<span class="c1"># This won't work
# arr = [[0] * 4] * 4
</span></pre></td></tr></tbody></table></code></pre></div></div>
<blockquote class="prompt-warn">
<p>python里面的区间基本上是左闭右开，比如range、slicing</p>
</blockquote>
<h2 id="tuples">Tuples</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c1"># Tuples are immutable lists
</span><span class="n">t</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

<span class="c1"># Essential Operations
</span><span class="n">t</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>      <span class="c1"># Count occurrences of value
</span><span class="n">t</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>      <span class="c1"># Find first index of value
</span>
<span class="c1"># Useful Patterns
</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>   <span class="c1"># Tuple unpacking
</span><span class="n">coords</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">)]</span>  <span class="c1"># Tuple in collections
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="sets">Sets</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="n">s</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">}</span>

<span class="c1"># Common Operations
</span><span class="n">s</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>             <span class="c1"># Add element
</span><span class="n">s</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>          <span class="c1"># Remove (raises error if missing)
</span><span class="n">s</span><span class="p">.</span><span class="nf">discard</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>         <span class="c1"># Remove (no error if missing)
</span><span class="n">s</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>              <span class="c1"># Remove and return arbitrary element
</span>
<span class="c1"># Set Operations
</span><span class="n">a</span><span class="p">.</span><span class="nf">union</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>           <span class="c1"># Elements in a OR b
</span><span class="n">a</span><span class="p">.</span><span class="nf">intersection</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>    <span class="c1"># Elements in a AND b
</span><span class="n">a</span><span class="p">.</span><span class="nf">difference</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>      <span class="c1"># Elements in a but NOT in b
</span><span class="n">a</span><span class="p">.</span><span class="nf">symmetric_difference</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>  <span class="c1"># Elements in a OR b but NOT both
</span><span class="n">a</span><span class="p">.</span><span class="nf">issubset</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>        <span class="c1"># True if all elements of a are in b
</span><span class="n">a</span><span class="p">.</span><span class="nf">issuperset</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>      <span class="c1"># True if all elements of b are in a
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="strings">Strings</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="n">s</span> <span class="o">=</span> <span class="sh">"</span><span class="s">hello world</span><span class="sh">"</span>

<span class="c1"># Essential Methods
</span><span class="n">s</span><span class="p">.</span><span class="nf">split</span><span class="p">()</span>            <span class="c1"># Split on whitespace
</span><span class="n">s</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">'</span><span class="s">,</span><span class="sh">'</span><span class="p">)</span>         <span class="c1"># Split on comma
</span><span class="n">s</span><span class="p">.</span><span class="nf">strip</span><span class="p">()</span>            <span class="c1"># Remove leading/trailing whitespace
</span><span class="n">s</span><span class="p">.</span><span class="nf">lower</span><span class="p">()</span>            <span class="c1"># Convert to lowercase
</span><span class="n">s</span><span class="p">.</span><span class="nf">upper</span><span class="p">()</span>            <span class="c1"># Convert to uppercase
</span><span class="n">s</span><span class="p">.</span><span class="nf">isalnum</span><span class="p">()</span>          <span class="c1"># Check if alphanumeric
</span><span class="n">s</span><span class="p">.</span><span class="nf">isalpha</span><span class="p">()</span>          <span class="c1"># Check if alphabetic
</span><span class="n">s</span><span class="p">.</span><span class="nf">isdigit</span><span class="p">()</span>          <span class="c1"># Check if all digits
</span><span class="n">s</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="sh">'</span><span class="s">sub</span><span class="sh">'</span><span class="p">)</span>        <span class="c1"># Index of substring (-1 if not found)
</span><span class="n">s</span><span class="p">.</span><span class="nf">count</span><span class="p">(</span><span class="sh">'</span><span class="s">sub</span><span class="sh">'</span><span class="p">)</span>       <span class="c1"># Count occurrences
</span><span class="n">s</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sh">'</span><span class="s">old</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">new</span><span class="sh">'</span><span class="p">)</span>  <span class="c1"># Replace all occurrences
</span>
<span class="c1"># ASCII Conversion
</span><span class="nf">ord</span><span class="p">(</span><span class="sh">'</span><span class="s">a</span><span class="sh">'</span><span class="p">)</span>             <span class="c1"># Char to ASCII (97)
</span><span class="nf">chr</span><span class="p">(</span><span class="mi">97</span><span class="p">)</span>              <span class="c1"># ASCII to char ('a')
</span>
<span class="c1"># Valid numeric strings can be converted
</span><span class="nf">print</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="sh">"</span><span class="s">123</span><span class="sh">"</span><span class="p">)</span> <span class="o">+</span> <span class="nf">int</span><span class="p">(</span><span class="sh">"</span><span class="s">123</span><span class="sh">"</span><span class="p">))</span>  <span class="c1"># 246
</span>
<span class="c1"># And numbers can be converted to strings
</span><span class="nf">print</span><span class="p">(</span><span class="nf">str</span><span class="p">(</span><span class="mi">123</span><span class="p">)</span> <span class="o">+</span> <span class="nf">str</span><span class="p">(</span><span class="mi">123</span><span class="p">))</span>  <span class="c1"># 123123
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="queues">Queues</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c1"># Queues (double ended queue)
</span><span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">deque</span>

<span class="c1"># Perfect for BFS - O(1) operations on both ends
</span><span class="n">d</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">()</span>
<span class="n">d</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>          <span class="c1"># Add right
</span><span class="n">d</span><span class="p">.</span><span class="nf">appendleft</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>      <span class="c1"># Add left
</span><span class="n">d</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>              <span class="c1"># Remove right
</span><span class="n">d</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>          <span class="c1"># Remove left
</span><span class="n">d</span><span class="p">.</span><span class="nf">extend</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">])</span>    <span class="c1"># Extend right
</span><span class="n">d</span><span class="p">.</span><span class="nf">extendleft</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">])</span><span class="c1"># Extend left
</span><span class="n">d</span><span class="p">.</span><span class="nf">rotate</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>          <span class="c1"># Rotate n steps right (negative for left)
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="heaps">Heaps</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">heapq</span>

<span class="c1"># MinHeap Operations - All O(log n) except heapify
</span><span class="n">nums</span> <span class="o">=</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">5</span><span class="p">]</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heapify</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>          <span class="c1"># Convert to heap in-place: O(n)
</span><span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>      <span class="c1"># Add element: O(log n)
</span><span class="n">smallest</span> <span class="o">=</span> <span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>  <span class="c1"># Remove smallest: O(log n)
</span>
<span class="c1"># MaxHeap Trick: Multiply by -1
</span><span class="n">nums</span> <span class="o">=</span> <span class="p">[</span><span class="o">-</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">nums</span><span class="p">]</span>    <span class="c1"># Convert to maxheap: O(n)
</span><span class="n">heapq</span><span class="p">.</span><span class="nf">heapify</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>          <span class="c1"># O(n)
</span><span class="n">largest</span> <span class="o">=</span> <span class="o">-</span><span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>  <span class="c1"># Get largest: O(log n)
</span>
<span class="c1"># Advanced Operations
</span><span class="n">k_largest</span> <span class="o">=</span> <span class="n">heapq</span><span class="p">.</span><span class="nf">nlargest</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">nums</span><span class="p">)</span>    <span class="c1"># O(n * log k)
</span><span class="n">k_smallest</span> <span class="o">=</span> <span class="n">heapq</span><span class="p">.</span><span class="nf">nsmallest</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">nums</span><span class="p">)</span>  <span class="c1"># O(n * log k)
</span>
<span class="c1"># Custom Priority Queue
</span><span class="n">heap</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">heap</span><span class="p">,</span> <span class="p">(</span><span class="n">priority</span><span class="p">,</span> <span class="n">item</span><span class="p">))</span>  <span class="c1"># Sort by priority
</span>
<span class="c1"># Under the hood are arrays
</span><span class="n">minHeap</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">minHeap</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">minHeap</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">minHeap</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>

<span class="c1"># Min is always at index 0
</span><span class="nf">print</span><span class="p">(</span><span class="n">minHeap</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>  <span class="c1"># 2
</span>
<span class="k">while</span> <span class="nf">len</span><span class="p">(</span><span class="n">minHeap</span><span class="p">):</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">minHeap</span><span class="p">))</span>
<span class="c1"># 2
# 3
# 4
</span>
<span class="c1"># No max heaps by default, work around is
# to use min heap and multiply by -1 when push &amp; pop.
</span><span class="n">maxHeap</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">maxHeap</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">)</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">maxHeap</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heappush</span><span class="p">(</span><span class="n">maxHeap</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">)</span>

<span class="c1"># Max is always at index 0
</span><span class="nf">print</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">*</span> <span class="n">maxHeap</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>  <span class="c1"># 4
</span>
<span class="k">while</span> <span class="nf">len</span><span class="p">(</span><span class="n">maxHeap</span><span class="p">):</span>
    <span class="nf">print</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">*</span> <span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">maxHeap</span><span class="p">))</span>
<span class="c1"># 4
# 3
# 2
</span>
<span class="c1"># Build heap from initial values
</span><span class="n">arr</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
<span class="n">heapq</span><span class="p">.</span><span class="nf">heapify</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
<span class="k">while</span> <span class="n">arr</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">heapq</span><span class="p">.</span><span class="nf">heappop</span><span class="p">(</span><span class="n">arr</span><span class="p">))</span>
<span class="c1"># 1
# 2
# 4
# 5
# 8
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="built-in-functions">Built-in Functions</h2>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="c1"># Iteration Helpers
</span><span class="nf">enumerate</span><span class="p">(</span><span class="n">lst</span><span class="p">)</span>        <span class="c1"># Index + value pairs
</span><span class="nf">zip</span><span class="p">(</span><span class="n">lst1</span><span class="p">,</span> <span class="n">lst2</span><span class="p">)</span>      <span class="c1"># Parallel iteration
</span><span class="nf">map</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="n">lst</span><span class="p">)</span>         <span class="c1"># Apply function to all elements
</span><span class="nf">filter</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="n">lst</span><span class="p">)</span>      <span class="c1"># Keep elements where fn returns True
</span><span class="nf">any</span><span class="p">(</span><span class="n">lst</span><span class="p">)</span>             <span class="c1"># True if any element is True
</span><span class="nf">all</span><span class="p">(</span><span class="n">lst</span><span class="p">)</span>             <span class="c1"># True if all elements are True
</span>
<span class="c1"># Binary Search
</span><span class="kn">import</span> <span class="n">bisect</span>

<span class="n">bisect</span><span class="p">.</span><span class="nf">bisect</span><span class="p">(</span><span class="n">lst</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>     <span class="c1"># Find insertion point
</span><span class="n">bisect</span><span class="p">.</span><span class="nf">bisect_left</span><span class="p">(</span><span class="n">lst</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span><span class="c1"># Find leftmost insertion point
</span><span class="n">bisect</span><span class="p">.</span><span class="nf">insort</span><span class="p">(</span><span class="n">lst</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>     <span class="c1"># Insert maintaining sort
</span>
<span class="c1"># Type Conversion
</span><span class="nf">int</span><span class="p">(</span><span class="sh">'</span><span class="s">42</span><span class="sh">'</span><span class="p">)</span>            <span class="c1"># String to int
</span><span class="nf">str</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>              <span class="c1"># Int to string
</span><span class="nf">list</span><span class="p">(</span><span class="sh">'</span><span class="s">abc</span><span class="sh">'</span><span class="p">)</span>          <span class="c1"># String to list
</span><span class="sh">''</span><span class="p">.</span><span class="nf">join</span><span class="p">([</span><span class="sh">'</span><span class="s">a</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">b</span><span class="sh">'</span><span class="p">])</span>   <span class="c1"># List to string
</span><span class="nf">set</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">])</span>         <span class="c1"># List to set
</span>
<span class="c1"># Math
</span><span class="nf">abs</span><span class="p">(</span><span class="o">-</span><span class="mi">5</span><span class="p">)</span>              <span class="c1"># Absolute value
</span><span class="nf">pow</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>            <span class="c1"># Power
</span><span class="nf">round</span><span class="p">(</span><span class="mf">3.14159</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>    <span class="c1"># Round to decimals
</span><span class="nf">divmod</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>        <span class="c1"># (3, 1) - returns (quotient, remainder)
</span>
<span class="c1"># Binary representation
</span><span class="nf">bin</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>              <span class="c1"># '0b1010'
</span><span class="nf">format</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">b</span><span class="sh">'</span><span class="p">)</span>      <span class="c1"># '1010' (without prefix)
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="math">Math</h2>
<p>在正数时，<code class="language-plaintext highlighter-rouge">int(a / b)</code> 和 <code class="language-plaintext highlighter-rouge">a // b</code> 通常结果相同，但在负数时可能不同：</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">int(a / b)</code> 是向零取整（<code class="language-plaintext highlighter-rouge">trunc</code>）。如果你希望总是向零取整，使用 <code class="language-plaintext highlighter-rouge">int(a / b)</code>。</li>
<li><code class="language-plaintext highlighter-rouge">//</code> 是向下取整（<code class="language-plaintext highlighter-rouge">floor</code>）。如果你希望总是向下取整，使用 <code class="language-plaintext highlighter-rouge">a // b</code>。</li>
</ul>
<p>另外，取模运算 <code class="language-plaintext highlighter-rouge">a % b</code>也遵从 <code class="language-plaintext highlighter-rouge">floor</code>法则，<code class="language-plaintext highlighter-rouge">a = (a // b) * b + (a % b)</code> -&gt; <code class="language-plaintext highlighter-rouge">a % b = a - (a // b) * b</code>。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="c1"># 向零取整
</span><span class="nf">print</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="mi">3</span> <span class="o">/</span> <span class="mi">2</span><span class="p">))</span>  <span class="c1"># 1
</span><span class="nf">print</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="o">-</span><span class="mi">3</span> <span class="o">/</span> <span class="mi">2</span><span class="p">))</span>  <span class="c1"># -1
</span>
<span class="c1"># 向下取整
</span><span class="nf">print</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="mi">3</span> <span class="o">//</span> <span class="mi">2</span><span class="p">))</span>  <span class="c1"># 1
</span><span class="nf">print</span><span class="p">(</span><span class="nf">int</span><span class="p">(</span><span class="o">-</span><span class="mi">3</span> <span class="o">//</span> <span class="mi">2</span><span class="p">))</span>  <span class="c1"># -2
# floor(-1.5) = -2
</span>
<span class="c1"># 取模
</span><span class="nf">print</span><span class="p">(</span><span class="mi">10</span> <span class="o">%</span> <span class="mi">3</span><span class="p">)</span>  <span class="c1"># 1
</span><span class="nf">print</span><span class="p">(</span><span class="o">-</span><span class="mi">10</span> <span class="o">%</span> <span class="mi">3</span><span class="p">)</span>  <span class="c1"># 2
# -10 - (-10//3) * 3 = -10 - (-4) * 3 = 2
</span>
<span class="kn">import</span> <span class="n">math</span>

<span class="c1"># Constants
</span><span class="n">math</span><span class="p">.</span><span class="n">pi</span>       <span class="c1"># 3.141592653589793
</span><span class="n">math</span><span class="p">.</span><span class="n">e</span>        <span class="c1"># 2.718281828459045
</span>
<span class="c1"># Common Functions
</span><span class="n">math</span><span class="p">.</span><span class="nf">ceil</span><span class="p">(</span><span class="mf">2.3</span><span class="p">)</span>        <span class="c1"># 3 - Smallest integer greater than x
</span><span class="n">math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="mf">2.3</span><span class="p">)</span>       <span class="c1"># 2 - Largest integer less than x
</span><span class="n">math</span><span class="p">.</span><span class="nf">gcd</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>        <span class="c1"># Greatest common divisor
</span><span class="n">math</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">base</span><span class="p">)</span>     <span class="c1"># Logarithm with specified base
</span><span class="n">math</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>          <span class="c1"># Square root
</span><span class="n">math</span><span class="p">.</span><span class="nf">pow</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>        <span class="c1"># x^y (prefer x ** y for integers)
</span>
<span class="c1"># Trigonometry
</span><span class="n">math</span><span class="p">.</span><span class="nf">degrees</span><span class="p">(</span><span class="n">rad</span><span class="p">)</span>     <span class="c1"># Convert radians to degrees
</span><span class="n">math</span><span class="p">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">deg</span><span class="p">)</span>     <span class="c1"># Convert degrees to radians
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/0xCodeFuture/CheetSheet-Python">CheetSheet-Python</a></li>
<li><a href="https://neetcode.io/courses/lessons/python-for-coding-interviews">neetcode</a></li>
</ul>]]></content><author><name>Jinchao Li</name></author><category term="Study"/><category term="Algorithm"/><category term="python"/><summary type="html"><![CDATA[Lists]]></summary></entry><entry><title type="html">算法基础</title><link href="https://jinchaoli.com/blog/%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/" rel="alternate" type="text/html" title="算法基础"/><published>2026-04-24T00:00:00+08:00</published><updated>2026-04-24T00:00:00+08:00</updated><id>https://jinchaoli.com/blog/%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80</id><content type="html" xml:base="https://jinchaoli.com/blog/%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/"><![CDATA[
<h2 id="框架概括">框架概括</h2>
<h3 id="整体框架">整体框架</h3>
<ul>
<li>数据结构（增删查改）
<ul>
<li>数组（顺序存储）
<ul>
<li>动态数组</li>
<li>字符串</li>
<li>哈希表</li>
<li>…</li>
</ul>
</li>
<li>链表（链式存储）
<ul>
<li>单/双链表</li>
<li>树</li>
<li>…</li>
</ul>
</li>
</ul>
</li>
<li>算法（穷举）
<ul>
<li>如何避免遗漏
<ul>
<li>回溯算法</li>
<li>动态规划</li>
<li>DFS</li>
<li>BFS</li>
<li>…</li>
</ul>
</li>
<li>如何避免冗余
<ul>
<li>二分</li>
<li>滑动窗口</li>
<li>贪心</li>
<li>…</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="各类数据结构的遍历">各类数据结构的遍历</h3>
<ol>
<li>数组的遍历，线性迭代结构：
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">traverse</span><span class="p">(</span><span class="n">arr</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]):</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)):</span>
        <span class="c1"># 迭代访问 arr[i]
</span></pre></td></tr></tbody></table></code></pre></div></div>
</li>
<li>链表的遍历，兼具迭代和递归结构：
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c1"># 基本的单链表节点
</span><span class="k">class</span> <span class="nc">ListNode</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">val</span><span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="n">val</span>
        <span class="n">self</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="bp">None</span>

<span class="k">def</span> <span class="nf">traverse</span><span class="p">(</span><span class="n">head</span><span class="p">:</span> <span class="n">ListNode</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">head</span>
    <span class="k">while</span> <span class="n">p</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="c1"># 迭代访问 p.val
</span>        <span class="n">p</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="nb">next</span>

<span class="k">def</span> <span class="nf">traverse</span><span class="p">(</span><span class="n">head</span><span class="p">:</span> <span class="n">ListNode</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="c1"># 递归访问 head.val
</span>    <span class="nf">traverse</span><span class="p">(</span><span class="n">head</span><span class="p">.</span><span class="nb">next</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
</li>
<li>二叉树的遍历，典型的非线性递归遍历结构：
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="c1"># 基本的二叉树节点
</span><span class="k">class</span> <span class="nc">TreeNode</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">val</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">left</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">right</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="n">self</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="n">val</span>
        <span class="n">self</span><span class="p">.</span><span class="n">left</span> <span class="o">=</span> <span class="n">left</span>
        <span class="n">self</span><span class="p">.</span><span class="n">right</span> <span class="o">=</span> <span class="n">right</span>

<span class="k">def</span> <span class="nf">traverse</span><span class="p">(</span><span class="n">root</span><span class="p">:</span> <span class="n">TreeNode</span><span class="p">):</span>
    <span class="nf">traverse</span><span class="p">(</span><span class="n">root</span><span class="p">.</span><span class="n">left</span><span class="p">)</span>
    <span class="nf">traverse</span><span class="p">(</span><span class="n">root</span><span class="p">.</span><span class="n">right</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
</li>
</ol>

<h2 id="算法复杂度">算法复杂度</h2>

<h3 id="主定理master-theorem">主定理（Master Theorem）</h3>
<p>假设有递归关系式：</p>
<p>$T(N) = aT(N/b) + f(N), f(N) = N^{\log_b(a)} \log^k(N)$</p>
<p>其中，$N$为问题规模，$a$为递归的子问题数量，$N/b$为每个子问题的规模（假设每个子问题的规模基本一样），$f(N)$为递归以外进行的计算工作。</p>
<p><strong>则其算法复杂度为</strong>：</p>
<p>$T(N) = O(N^{\log_b(a)} \log^{(k+1)}N)$</p>
<h3 id="常见算法复杂度">常见算法复杂度</h3>
<table>
<thead>
<tr>
<th>算法</th>
<th>递归关系式</th>
<th>复杂度</th>
</tr>
</thead>
<tbody>
<tr>
<td>二分查找</td>
<td>$T(N) = T(N/2) + O(1)$</td>
<td>$O(\log(N))$</td>
</tr>
<tr>
<td>二叉树遍历</td>
<td>$T(N) = 2T(N/2) + O(1)$</td>
<td>$O(N)$</td>
</tr>
<tr>
<td>归并排序</td>
<td>$T(N) = 2T(N/2) + O(N)$</td>
<td>$O(N\log(N))$</td>
</tr>
</tbody>
</table>

<h2 id="双指针">双指针</h2>

<p>使用两个指针变量在数组或链表等线性结构上协同移动，避免嵌套循环，将部分 $O(N^2)$ 的算法优化为 $O(N)$。主要分为：</p>
<ul>
<li>同向双指针（快慢指针）：一个快指针先行，慢指针跟进，常用于滑动窗口（去重）、链表操作（找中点、判断环、环入口）等。</li>
<li>相向双指针（对撞指针）：从两端向中间移动，常用于有序数组求和、回文判断、反转数组、数组合并等。</li>
<li>背向双指针：从中间向两边扩展，常用于回文串、最长子回文等问题。</li>
</ul>
<h3 id="算法复杂度-1">算法复杂度</h3>
<p>通常情况下，时间复杂度 $O(N)$（与最内层循环主体的执行次数有关），空间复杂度：$O(1)$。</p>
<h3 id="使用场景">使用场景</h3>
<ul>
<li>滑动窗口 (90%)</li>
<li>时间复杂度要求 $O(N)$ (80%是双指针)</li>
<li>要求原地操作，只可以使用交换，不能使用额外空间 (80%)</li>
<li>有子数组 subarray / 子字符串 substring 的关键词 (50%)</li>
<li>有回文 Palindrome 关键词(50%)</li>
</ul>
<h3 id="代码模板">代码模板</h3>
<ul>
<li>初始化指针：<code class="language-plaintext highlighter-rouge">left</code>, <code class="language-plaintext highlighter-rouge">right</code>根据方向设置起点</li>
<li>循环控制：<code class="language-plaintext highlighter-rouge">while</code>或<code class="language-plaintext highlighter-rouge">for</code>控制移动（比如<code class="language-plaintext highlighter-rouge">right</code>扩展，<code class="language-plaintext highlighter-rouge">left</code>收缩）</li>
<li>状态更新：维护当前窗口或配对状态，根据条件分类讨论</li>
<li>结果记录：更新答案（相等时、满足条件时）</li>
<li>边界处理：空数组、单元素、去重跳过等</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="c1"># 通用双指针框架（适用于数组/列表）
</span><span class="k">def</span> <span class="nf">two_pointers</span><span class="p">(</span><span class="n">arr</span><span class="p">):</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">0</span>  <span class="c1"># 或其他默认值
</span>
    <span class="c1"># Step 1: 初始化指针
</span>    <span class="n">left</span> <span class="o">=</span> <span class="mi">0</span>                    <span class="c1"># 左指针 / 慢指针
</span>    <span class="c1"># right = 0 或 n - 1，根据方向选择
</span>
    <span class="c1"># Step 2: 根据类型选择遍历结构
</span>    <span class="k">for</span> <span class="n">right</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>      <span class="c1"># 同向：快慢指针；滑动窗口
</span>    <span class="c1"># while left &lt; right:       # 相向：对撞指针（常用于有序数组）
</span>    <span class="c1"># while left &lt; n:           # 其他控制条件
</span>
        <span class="c1"># Step 3: 扩展或移动右指针后，处理当前窗口/状态
</span>        <span class="c1"># ... 更新状态
</span>
        <span class="c1"># Step 4: 判断是否需要收缩左指针（滑动窗口类）
</span>        <span class="k">while</span> <span class="n">left</span> <span class="o">&lt;=</span> <span class="n">right</span> <span class="ow">and</span> <span class="nf">need_to_move_left</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
            <span class="c1"># ... 更新或记录结果
</span>            <span class="n">left</span> <span class="o">+=</span> <span class="mi">1</span>

        <span class="c1"># 或：根据条件移动双指针（对撞类）
</span>        <span class="c1"># if condition:
</span>        <span class="c1">#     left += 1
</span>        <span class="c1"># else:
</span>        <span class="c1">#     right -= 1
</span>
    <span class="k">return</span> <span class="n">result</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="例题">例题</h3>
<p><a href="https://leetcode.cn/problems/merge-sorted-array">88. 合并两个有序数组</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你两个按 非递减顺序 排列的整数数组 `nums1` 和 `nums2`，另有两个整数 `m` 和 `n`，分别表示 `nums1` 和 `nums2` 中的元素数目。
    请你 合并 `nums2` 到 `nums1` 中，使合并后的数组同样按 非递减顺序 排列。
🧪样例：输入：nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3；输出：[1,2,2,3,5,6]
💡难点：从后往前操作以便直接覆盖。
</span><span class="sh">"""</span>

<span class="k">def</span> <span class="nf">merge</span><span class="p">(</span><span class="n">nums1</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">m</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">nums2</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">
    Do not return anything, modify nums1 in-place instead.
    </span><span class="sh">"""</span>
    <span class="c1"># 逆向双指针，从后往前操作可以直接覆盖
</span>    <span class="n">p1</span><span class="p">,</span> <span class="n">p2</span> <span class="o">=</span> <span class="n">m</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span>  <span class="c1"># 同向，但是从后往前
</span>    <span class="n">tail</span> <span class="o">=</span> <span class="n">m</span> <span class="o">+</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span>  <span class="c1"># 需要维护的状态：当前需要处理的索引
</span>    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">p1</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">p2</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">break</span>
        <span class="k">if</span> <span class="n">nums1</span><span class="p">[</span><span class="n">p1</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">nums2</span><span class="p">[</span><span class="n">p2</span><span class="p">]:</span>
            <span class="n">nums1</span><span class="p">[</span><span class="n">tail</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums2</span><span class="p">[</span><span class="n">p2</span><span class="p">]</span>
            <span class="n">p2</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="n">tail</span> <span class="o">-=</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">nums1</span><span class="p">[</span><span class="n">tail</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums1</span><span class="p">[</span><span class="n">p1</span><span class="p">]</span>
            <span class="n">p1</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="n">tail</span> <span class="o">-=</span> <span class="mi">1</span>
    <span class="c1"># 由于比较，总会有一个数组先结束，对于后结束的一个数组：这里肯定是p2
</span>    <span class="k">if</span> <span class="n">p2</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">:</span>
        <span class="n">nums1</span><span class="p">[:</span> <span class="n">p2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums2</span><span class="p">[:</span> <span class="n">p2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">merge</span><span class="p">(</span><span class="n">nums1</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">nums2</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
    <span class="sh">"""</span><span class="s"> 合并双指针，非原地操作。
    🧪样例：输入：nums1 = [1,2,3], nums2 = [2,5,6]；输出：[1,2,2,3,5,6]
    </span><span class="sh">"""</span>
    <span class="n">m</span><span class="p">,</span> <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">num1</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums2</span><span class="p">)</span>
    <span class="n">new_list</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span>
    <span class="c1"># 合并的过程只能操作 i, j 的移动，不要去用 list1.pop(0) 之类的操作
</span>    <span class="c1"># 因为 pop(0) 是 O(n) 的时间复杂度，而且会改变序号
</span>    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">m</span> <span class="ow">or</span> <span class="n">j</span> <span class="o">&gt;=</span> <span class="n">n</span><span class="p">:</span>
            <span class="k">break</span>
        <span class="k">if</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]:</span>
            <span class="n">new_list</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">new_list</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">])</span>
            <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="c1"># 合并剩下的数到 new_list 里
</span>    <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">m</span><span class="p">:</span>
        <span class="n">new_list</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">:</span>
        <span class="n">new_list</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">])</span>
        <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">new_list</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/merge-two-sorted-lists">21. 合并两个有序链表</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
</pre></td><td class="rouge-code"><pre><span class="c1"># Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
</span><span class="k">def</span> <span class="nf">mergeTwoLists</span><span class="p">(</span><span class="n">list1</span><span class="p">:</span> <span class="n">ListNode</span><span class="p">,</span> <span class="n">list2</span><span class="p">:</span> <span class="n">ListNode</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ListNode</span><span class="p">:</span>
    <span class="n">dummy</span> <span class="o">=</span> <span class="nc">ListNode</span><span class="p">()</span>  <span class="c1"># 虚拟头结点，它的唯一作用就是提供一个起始点，让 p 可以不断向后连接节点
</span>    <span class="n">p</span> <span class="o">=</span> <span class="n">dummy</span>  <span class="c1"># p 指向虚拟链表的末尾
</span>    <span class="n">p1</span> <span class="o">=</span> <span class="n">l1</span>
    <span class="n">p2</span> <span class="o">=</span> <span class="n">l2</span>

    <span class="k">while</span> <span class="n">p1</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">p2</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="c1"># 比较 p1 和 p2 两个指针
</span>        <span class="c1"># 将值较小的的节点接到 p 指针
</span>        <span class="k">if</span> <span class="n">p1</span><span class="p">.</span><span class="n">val</span> <span class="o">&gt;</span> <span class="n">p2</span><span class="p">.</span><span class="n">val</span><span class="p">:</span>
            <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">p2</span>
            <span class="n">p2</span> <span class="o">=</span> <span class="n">p2</span><span class="p">.</span><span class="nb">next</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">p1</span>
            <span class="n">p1</span> <span class="o">=</span> <span class="n">p1</span><span class="p">.</span><span class="nb">next</span>
        <span class="c1"># p 指针不断前进
</span>        <span class="n">p</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="nb">next</span>

    <span class="k">if</span> <span class="n">p1</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">p1</span>

    <span class="k">if</span> <span class="n">p2</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">p2</span>

    <span class="k">return</span> <span class="n">dummy</span><span class="p">.</span><span class="nb">next</span>  <span class="c1"># 注意：不是返回指针 p，而是返回链表的头部，也就是 dummy.next
</span></pre></td></tr></tbody></table></code></pre></div></div>
<blockquote class="prompt-info">
<p>虚拟头结点</p>
</blockquote>
<p><br/></p>
<p><a href="https://leetcode.cn/problems/longest-palindromic-substring">5. 最长回文子串</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个字符串 `s`，找到 `s` 中最长的 回文 子串。
🧪样例：输入s = </span><span class="sh">"</span><span class="s">babad</span><span class="sh">"</span><span class="s">；输出</span><span class="sh">"</span><span class="s">bab</span><span class="sh">"</span><span class="s">或</span><span class="sh">"</span><span class="s">aba</span><span class="sh">"</span><span class="s">。输入：s = </span><span class="sh">"</span><span class="s">cbbd</span><span class="sh">"</span><span class="s">；输出：</span><span class="sh">"</span><span class="s">bb</span><span class="sh">"</span><span class="s">。
💡重点：
- 需要同时考虑奇数和偶数长的回文串
- 中心扩散
- 这题还可以用动态规划解:
    - 状态定义：dp[i][j]表示s[i:j+1]是否为回文
    - 初始化：dp = [[False for _ in range(size)] for _ in range(size)]
    - 转移方程：dp[i][j] = dp[i-1][j-1] and s[i] == s[j]
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">longestPalindrome</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="mi">1</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">s</span>
    <span class="n">max_s</span><span class="p">,</span> <span class="n">max_len</span> <span class="o">=</span> <span class="sh">""</span><span class="p">,</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="p">(</span><span class="n">max_len</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">:</span>
            <span class="k">break</span>  <span class="c1"># 提前终止
</span>        <span class="c1"># 处理奇数长度的回文子串，以i为中心向两边移动
</span>        <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="n">i</span><span class="p">,</span> <span class="n">i</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">left</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">right</span> <span class="o">&gt;</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
                <span class="k">break</span>
            <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">left</span><span class="p">]</span> <span class="o">==</span> <span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">]:</span>
                <span class="n">left</span> <span class="o">-=</span> <span class="mi">1</span>
                <span class="n">right</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="k">break</span>  <span class="c1"># 注意所有break的情况
</span>        <span class="n">cur_len</span> <span class="o">=</span> <span class="n">right</span> <span class="o">-</span> <span class="n">left</span> <span class="o">-</span> <span class="mi">1</span>
        <span class="k">if</span> <span class="n">cur_len</span> <span class="o">&gt;</span> <span class="n">max_len</span><span class="p">:</span>
            <span class="n">max_s</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">left</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">:</span> <span class="n">right</span><span class="p">]</span>
            <span class="n">max_len</span> <span class="o">=</span> <span class="n">cur_len</span>
        <span class="c1"># 处理偶数长度的回文子串
</span>        <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="n">i</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">left</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">right</span> <span class="o">&gt;</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
                <span class="k">break</span>
            <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">left</span><span class="p">]</span> <span class="o">==</span> <span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">]:</span>
                <span class="n">left</span> <span class="o">-=</span> <span class="mi">1</span>
                <span class="n">right</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="k">break</span>
        <span class="n">cur_len</span> <span class="o">=</span> <span class="n">right</span> <span class="o">-</span> <span class="n">left</span> <span class="o">-</span> <span class="mi">1</span>
        <span class="k">if</span> <span class="n">cur_len</span> <span class="o">&gt;</span> <span class="n">max_len</span><span class="p">:</span>
            <span class="n">max_s</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">left</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">:</span> <span class="n">right</span><span class="p">]</span>
            <span class="n">max_len</span> <span class="o">=</span> <span class="n">cur_len</span>
    <span class="k">return</span> <span class="n">max_s</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/binary-subarrays-with-sum">930. 和相同的二元子数组</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个二元数组 `nums`，和一个整数 `goal`，请你统计并返回有多少个和为 `goal` 的非空子数组。
🧪样例：
    输入：nums = [1,0,1,0,1], goal = 2
    输出：4
    解释：有 4 个满足题目要求的子数组：[1,0,1]、[1,0,1,0]、[0,1,0,1]、[1,0,1]
💡重点：
    1. 可以用前缀和 + 哈希表，类似两数之和
    2. 也可以用滑动窗口，因为元素都是非负的（只有0和1）
</span><span class="sh">"""</span>

<span class="c1"># 方法1：前缀和 + 哈希表，时间 O(N)，空间 O(N)
</span><span class="k">def</span> <span class="nf">numSubarraysWithSum</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">goal</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">defaultdict</span>
    <span class="n">prefix_sum</span> <span class="o">=</span> <span class="nf">defaultdict</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span>
    <span class="n">prefix_sum</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>  <span class="c1"># 前缀和为0的有1个（空前缀）
</span>    <span class="n">cur_sum</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">nums</span><span class="p">:</span>
        <span class="n">cur_sum</span> <span class="o">+=</span> <span class="n">num</span>
        <span class="c1"># 需要找之前的前缀和 = cur_sum - goal
</span>        <span class="n">count</span> <span class="o">+=</span> <span class="n">prefix_sum</span><span class="p">[</span><span class="n">cur_sum</span> <span class="o">-</span> <span class="n">goal</span><span class="p">]</span>
        <span class="n">prefix_sum</span><span class="p">[</span><span class="n">cur_sum</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">count</span>

<span class="c1"># 方法2：滑动窗口，时间 O(N)，空间 O(1)
# 由于元素非负，可以利用滑动窗口
# atMost(goal) 返回和 &lt;= goal 的子数组个数
# 答案 = atMost(goal) - atMost(goal - 1)
</span><span class="k">def</span> <span class="nf">numSubarraysWithSum</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">goal</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">atMost</span><span class="p">(</span><span class="n">goal</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">goal</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">return</span> <span class="mi">0</span>
        <span class="n">left</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="n">cur_sum</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">right</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)):</span>
            <span class="n">cur_sum</span> <span class="o">+=</span> <span class="n">nums</span><span class="p">[</span><span class="n">right</span><span class="p">]</span>
            <span class="k">while</span> <span class="n">cur_sum</span> <span class="o">&gt;</span> <span class="n">goal</span><span class="p">:</span>
                <span class="n">cur_sum</span> <span class="o">-=</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span>
                <span class="n">left</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="n">count</span> <span class="o">+=</span> <span class="n">right</span> <span class="o">-</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span>  <span class="c1"># 以 right 结尾的子数组个数
</span>        <span class="k">return</span> <span class="n">count</span>

    <span class="k">return</span> <span class="nf">atMost</span><span class="p">(</span><span class="n">goal</span><span class="p">)</span> <span class="o">-</span> <span class="nf">atMost</span><span class="p">(</span><span class="n">goal</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>


<h2 id="滑动窗口">滑动窗口</h2>
<p>滑动窗口可以归为快慢双指针，一快一慢两个指针前后相随，中间的部分就是窗口。滑动窗口算法技巧主要用来解决子数组问题，比如让你寻找符合某个条件的最长/最短子数组。</p>
<p>与普通的快慢指针（嵌套循环，$O(N^2)$）不同的是，滑动窗口（队列）维护的元素只进入/移出一次（指针 left, right 只增不减），所以复杂度为$O(N)$。算法的重点在于判断是否要把 left 移动。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
</pre></td><td class="rouge-code"><pre><span class="c1"># 滑动窗口模板
</span><span class="k">def</span> <span class="nf">sliding_window</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="c1"># 用合适的数据结构记录窗口中的数据，根据具体场景变通
</span>    <span class="c1"># 比如说，我想记录窗口中元素出现的次数，就用 map
</span>    <span class="c1"># 如果我想记录窗口中的元素和，就可以只用一个 int
</span>    <span class="n">window</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>

    <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">right</span> <span class="o">&lt;</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
        <span class="c1"># c 是将移入窗口的字符
</span>        <span class="n">c</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">]</span>
        <span class="n">window</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
        <span class="c1"># 增大窗口
</span>        <span class="n">right</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="c1"># 进行窗口内数据的一系列更新
</span>        <span class="bp">...</span>

        <span class="c1"># 判断左侧窗口是否要收缩
</span>        <span class="k">while</span> <span class="n">left</span> <span class="o">&lt;</span> <span class="n">right</span> <span class="ow">and</span> <span class="nf">window_needs_shrink</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
            <span class="c1"># 把 s[left] 移出窗口
</span>            <span class="n">window</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">left</span><span class="p">])</span>
            <span class="c1"># 缩小窗口
</span>            <span class="n">left</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="c1"># 进行窗口内数据的一系列更新
</span>            <span class="bp">...</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>基于这个框架，遇到子串/子数组相关的题目，你只需要回答以下三个问题：</p>
<ol>
<li>什么时候应该移动 right 扩大窗口？窗口加入字符时，应该更新哪些数据？</li>
<li>什么时候窗口应该暂停扩大，开始移动 left 缩小窗口？从窗口移出字符时，应该更新哪些数据？</li>
<li>什么时候应该更新结果？</li>
</ol>
<h3 id="例题-1">例题</h3>
<p><a href="https://leetcode.cn/problems/longest-substring-without-repeating-characters">3. 无重复字符的最长子串</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定一个字符串 s ，请你找出其中不含有重复字符的 最长 子串 的长度。
🧪样例：
    输入: s = </span><span class="sh">"</span><span class="s">abcabcbb</span><span class="sh">"</span><span class="s">
    输出: 3
    解释: 因为无重复字符的最长子串是 </span><span class="sh">"</span><span class="s">abc</span><span class="sh">"</span><span class="s">，所以其长度为 3。注意 </span><span class="sh">"</span><span class="s">bca</span><span class="sh">"</span><span class="s"> 和 </span><span class="sh">"</span><span class="s">cab</span><span class="sh">"</span><span class="s"> 也是正确答案。
💡重点：滑动窗口最重要的是指针只增不减
</span><span class="sh">"""</span>
<span class="c1"># 错误用法
# 下面这种写法没有保证每个元素只处理一次（l递增，但r会回退），就是暴力的嵌套循环，复杂度为 O(N^2)
</span><span class="k">def</span> <span class="nf">lengthOfLongestSubstring</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">N</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
    <span class="n">ans</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
        <span class="n">substr</span> <span class="o">=</span> <span class="p">{</span><span class="n">s</span><span class="p">[</span><span class="n">l</span><span class="p">]}</span>
        <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">N</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">r</span><span class="p">]</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">substr</span><span class="p">:</span>
                <span class="n">substr</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">r</span><span class="p">])</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="k">break</span>
        <span class="n">ans</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">ans</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">substr</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">ans</span>

<span class="c1"># 正确用法 1
# r只增不减：O(N), 23ms
</span><span class="k">def</span> <span class="nf">lengthOfLongestSubstring</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">N</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
    <span class="n">right</span><span class="p">,</span> <span class="n">ans</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">left</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
        <span class="n">substr</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">left</span><span class="p">:</span><span class="n">right</span><span class="p">]</span>
        <span class="k">while</span> <span class="bp">True</span> <span class="ow">and</span> <span class="n">right</span> <span class="o">&lt;</span> <span class="n">N</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">]</span> <span class="ow">in</span> <span class="n">substr</span><span class="p">:</span>
                <span class="k">break</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">right</span> <span class="o">+=</span> <span class="mi">1</span>
                <span class="n">substr</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">left</span><span class="p">:</span><span class="n">right</span><span class="p">]</span>
        <span class="n">ans</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">ans</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">substr</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">ans</span>

<span class="c1"># 正确用法 2
# window维护每个字符出现的次数, 删除 s[left] 直至 win[s[right]] &lt;= 1, O(N), 76ms
</span><span class="k">def</span> <span class="nf">lengthOfLongestSubstring</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">win</span><span class="p">,</span> <span class="n">ans</span> <span class="o">=</span> <span class="nf">dict</span><span class="p">(),</span> <span class="mi">0</span>
    <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">right</span> <span class="o">&lt;</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
        <span class="n">win</span><span class="p">[</span><span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">]]</span> <span class="o">=</span> <span class="n">win</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">],</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="c1"># 滑动左指针直到win[s[right]]&lt;=1
</span>        <span class="k">while</span> <span class="n">win</span><span class="p">[</span><span class="n">s</span><span class="p">[</span><span class="n">right</span><span class="p">]]</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="n">win</span><span class="p">[</span><span class="n">s</span><span class="p">[</span><span class="n">left</span><span class="p">]]</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="n">left</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">ans</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">ans</span><span class="p">,</span> <span class="n">right</span> <span class="o">-</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">right</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">ans</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/permutation-in-string">567. 字符串的排列</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你两个字符串 s1 和 s2 ，写一个函数来判断 s2 是否包含 s1 的 排列。如果是，返回 true ；否则，返回 false 。
🧪样例：
    输入：s1 = </span><span class="sh">"</span><span class="s">abb</span><span class="sh">"</span><span class="s"> s2 = </span><span class="sh">"</span><span class="s">eidbabooo</span><span class="sh">"</span><span class="s">
    输出：true
    解释：s2 包含 s1 的排列之一 (</span><span class="sh">"</span><span class="s">bab</span><span class="sh">"</span><span class="s">).
💡重点：
</span><span class="sh">"""</span>

<span class="c1"># 错误解法
# 暴力枚举，每次循环都重新计算Counter，时间复杂度 O(N^2)
</span><span class="k">def</span> <span class="nf">checkInclusion</span><span class="p">(</span><span class="n">s1</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">s2</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">Counter</span>

    <span class="n">N</span><span class="p">,</span> <span class="n">M</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">s1</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
    <span class="n">ref</span> <span class="o">=</span> <span class="nc">Counter</span><span class="p">(</span><span class="n">s1</span><span class="p">)</span>
    <span class="c1"># 窗长始终为N
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">M</span><span class="p">):</span>
        <span class="n">substr</span> <span class="o">=</span> <span class="n">s2</span><span class="p">[</span><span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">+</span> <span class="n">N</span><span class="p">]</span>
        <span class="n">win</span> <span class="o">=</span> <span class="nc">Counter</span><span class="p">(</span><span class="n">substr</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">win</span> <span class="o">==</span> <span class="n">ref</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">True</span>
    <span class="k">return</span> <span class="bp">False</span>

<span class="c1"># 正确解法
# 不需要每次都重新计算，只需要更新 left 和 right 的计数，时间复杂度 O(N)
</span><span class="k">def</span> <span class="nf">checkInclusion</span><span class="p">(</span><span class="n">s1</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">s2</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="n">N</span><span class="p">,</span> <span class="n">M</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">s1</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">N</span> <span class="o">&gt;</span> <span class="n">M</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">False</span>

    <span class="n">ref</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">s1</span><span class="p">:</span>
        <span class="n">ref</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="n">ref</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>

    <span class="n">window</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">N</span><span class="p">):</span>
        <span class="n">window</span><span class="p">[</span><span class="n">s2</span><span class="p">[</span><span class="n">i</span><span class="p">]]</span> <span class="o">=</span> <span class="n">window</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">s2</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>

    <span class="n">right</span> <span class="o">=</span> <span class="n">N</span>
    <span class="k">while</span> <span class="n">right</span> <span class="o">&lt;</span> <span class="n">M</span><span class="p">:</span>
        <span class="c1"># print(window)
</span>        <span class="k">if</span> <span class="n">window</span> <span class="o">==</span> <span class="n">ref</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">True</span>
        <span class="n">new</span> <span class="o">=</span> <span class="n">s2</span><span class="p">[</span><span class="n">right</span><span class="p">]</span>
        <span class="n">old</span> <span class="o">=</span> <span class="n">s2</span><span class="p">[</span><span class="n">right</span> <span class="o">-</span> <span class="n">N</span><span class="p">]</span>
        <span class="c1"># 维护滑动窗
</span>        <span class="n">window</span><span class="p">[</span><span class="n">new</span><span class="p">]</span> <span class="o">=</span> <span class="n">window</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">new</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="n">window</span><span class="p">[</span><span class="n">old</span><span class="p">]</span> <span class="o">=</span> <span class="n">window</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="n">old</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>
        <span class="k">if</span> <span class="n">window</span><span class="p">[</span><span class="n">old</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="k">del</span> <span class="n">window</span><span class="p">[</span><span class="n">old</span><span class="p">]</span>
        <span class="n">right</span> <span class="o">+=</span> <span class="mi">1</span>

    <span class="k">if</span> <span class="n">window</span> <span class="o">==</span> <span class="n">ref</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">True</span>
    <span class="k">return</span> <span class="bp">False</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="查找">查找</h2>

<p>查找是最基础操作，其中最常用的是二分查找，即从有序数组<code class="language-plaintext highlighter-rouge">array</code>中直接寻找某个值<code class="language-plaintext highlighter-rouge">query</code>对应的<code class="language-plaintext highlighter-rouge">index</code>。一般解法：</p>
<ul>
<li>双指针：比较<code class="language-plaintext highlighter-rouge">array[mid]</code>和<code class="language-plaintext highlighter-rouge">query</code>的大小（<code class="language-plaintext highlighter-rouge">mid = low + (high-low)//2</code>），从而更新左右指针<code class="language-plaintext highlighter-rouge">low</code>、<code class="language-plaintext highlighter-rouge">high</code>，终止条件:
<ul>
<li>(1) 找到了<code class="language-plaintext highlighter-rouge">query</code>（<code class="language-plaintext highlighter-rouge">array[mid] = query</code>）</li>
<li>(2) 左右指针相遇（<code class="language-plaintext highlighter-rouge">low &gt; high</code>）</li>
</ul>
</li>
<li>递归：分成左右两子数组，如果<code class="language-plaintext highlighter-rouge">array[mid]</code>不等于<code class="language-plaintext highlighter-rouge">query</code>则不断在左或者右子数组里面查找，直到找到了<code class="language-plaintext highlighter-rouge">query</code>或者子数组为空。</li>
</ul>
<p>重点在于分类讨论，建议用双闭区间，仔细讨论<code class="language-plaintext highlighter-rouge">array[low:mid]</code>, <code class="language-plaintext highlighter-rouge">array[mid]</code>, <code class="language-plaintext highlighter-rouge">array[mid+1:high+1]</code>的情况，并注意三个数组是否为空。</p>
<h3 id="算法复杂度-2">算法复杂度</h3>
<p>时间 $O(\log(N))$。每次只需要查一边，所以子问题数量为1。空间 $O(1)$。</p>
<h3 id="使用场景-1">使用场景</h3>
<ul>
<li>当数组已经排好序 (30-40%是二分)</li>
<li>当面试官要求你找一个比 $O(N)$ 更小的时间复杂度算法的时候(99%)</li>
<li>找到数组中的一个分割位置，使得左半部分满足某个条件，右半部分不满足(100%)</li>
<li>找到一个最大/最小的值使得某个条件被满足(90%)</li>
</ul>
<h3 id="代码模板-1">代码模板</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">hash_search</span><span class="p">(</span><span class="n">arr</span><span class="p">,</span> <span class="n">query</span><span class="p">):</span>
    <span class="c1"># 哈希查找，用于无序数组
</span>    <span class="n">seen</span> <span class="o">=</span> <span class="p">{}</span>
    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">val</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">arr</span><span class="p">):</span>
        <span class="n">complement</span> <span class="o">=</span> <span class="n">query</span> <span class="o">-</span> <span class="n">val</span>
        <span class="k">if</span> <span class="n">complement</span> <span class="ow">in</span> <span class="n">seen</span><span class="p">:</span>
            <span class="k">return</span> <span class="p">[</span><span class="n">seen</span><span class="p">[</span><span class="n">complement</span><span class="p">],</span> <span class="n">i</span><span class="p">]</span>  <span class="c1"># 如两数之和
</span>        <span class="n">seen</span><span class="p">[</span><span class="n">val</span><span class="p">]</span> <span class="o">=</span> <span class="n">i</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>

<span class="k">def</span> <span class="nf">binary_search</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">query</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s"> Two points. [low, high] will be splitted:
        (1) [low, mid - 1]
        (2) [mid]
        (3) [mid + 1, high]
    </span><span class="sh">"""</span>
    <span class="n">low</span><span class="p">,</span> <span class="n">high</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">array</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>  <span class="c1"># 闭区间 [left, right]
</span>    <span class="k">while</span> <span class="n">low</span> <span class="o">&lt;=</span> <span class="n">high</span><span class="p">:</span>
        <span class="n">mid</span> <span class="o">=</span> <span class="n">low</span> <span class="o">+</span> <span class="p">(</span><span class="n">high</span> <span class="o">-</span> <span class="n">low</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>  <span class="c1"># 防溢出
</span>        <span class="n">val</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="n">mid</span><span class="p">]</span>
        <span class="c1"># array[low:mid], array[mid], array[mid+1:high+1]
</span>        <span class="k">if</span> <span class="n">val</span> <span class="o">==</span> <span class="n">query</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">mid</span>
        <span class="k">if</span> <span class="n">val</span> <span class="o">&lt;</span> <span class="n">query</span><span class="p">:</span>
            <span class="n">low</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">high</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="bp">None</span>

<span class="k">def</span> <span class="nf">binary_search_recur</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">low</span><span class="p">,</span> <span class="n">high</span><span class="p">,</span> <span class="n">query</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s"> Recurrence. [low, high] will be splitted:
        (1) [low, mid - 1]
        (2) [mid]
        (3) [mid + 1, high]
    </span><span class="sh">"""</span>
    <span class="k">if</span> <span class="n">low</span> <span class="o">&gt;</span> <span class="n">high</span><span class="p">:</span>
        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>
    <span class="n">mid</span> <span class="o">=</span> <span class="n">low</span> <span class="o">+</span> <span class="p">(</span><span class="n">high</span> <span class="o">-</span> <span class="n">low</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>   <span class="c1"># This mid will not break integer range
</span>    <span class="k">if</span> <span class="n">query</span> <span class="o">&lt;</span> <span class="n">array</span><span class="p">[</span><span class="n">mid</span><span class="p">]:</span>
        <span class="k">return</span> <span class="nf">binary_search_recur</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">low</span><span class="p">,</span> <span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">query</span><span class="p">)</span>  <span class="c1"># Go search in the left subarray
</span>    <span class="k">if</span> <span class="n">query</span> <span class="o">&gt;</span> <span class="n">array</span><span class="p">[</span><span class="n">mid</span><span class="p">]:</span>
        <span class="k">return</span> <span class="nf">binary_search_recur</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">high</span><span class="p">,</span> <span class="n">query</span><span class="p">)</span>  <span class="c1"># Go search in the right subarray
</span>    <span class="k">return</span> <span class="n">mid</span>  <span class="c1"># `array[mid] = query`, stop recurrence
</span></pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="例题-2">例题</h3>
<p><a href="https://leetcode.cn/problems/search-in-rotated-sorted-array">33. 搜索旋转排序数组</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定旋转后的数组 `nums` 和一个整数 `target`，如果 `nums` 中存在这个目标值 `target`，则返回它的下标，否则返回 `-1`。
🧪样例：输入：`nums = [2,3,4,5,6,7,0,1]`, `target = 0`；输出：`target`的下标为`6`。
💡重点：
1. 数组不是有序的，但是是局部有序的。有序的那端一定是最左边小于最右边，无序的那端一定是最左边大于最右边。
2. 目标是否在有序部分比较好判断`nums[left_] &lt;= target and target &lt; nums[right_]`，如果不满足则落在另一边。
&lt;https://leetcode.cn/problems/search-in-rotated-sorted-array/solutions/2636954/javapython3cer-fen-cha-zhao-you-xu-de-ba-5g7e&gt;
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">target</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">low</span><span class="p">,</span> <span class="n">high</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">low</span> <span class="o">&lt;=</span> <span class="n">high</span><span class="p">:</span>
        <span class="n">mid</span> <span class="o">=</span> <span class="n">low</span> <span class="o">+</span> <span class="p">(</span><span class="n">high</span> <span class="o">-</span> <span class="n">low</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
        <span class="n">val</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">mid</span><span class="p">]</span>
        <span class="c1"># print(mid, nums[low:mid], nums[mid], nums[mid+1:high+1])
</span>        <span class="k">if</span> <span class="n">val</span> <span class="o">==</span> <span class="n">target</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">mid</span>
        <span class="k">if</span> <span class="n">low</span> <span class="o">&lt;</span> <span class="n">mid</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">low</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">nums</span><span class="p">[</span><span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]:</span>
            <span class="c1"># 左边有序，先判断是否在左边
</span>            <span class="k">if</span> <span class="n">nums</span><span class="p">[</span><span class="n">low</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">target</span> <span class="ow">and</span> <span class="n">target</span> <span class="o">&lt;=</span> <span class="n">nums</span><span class="p">[</span><span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]:</span>
                <span class="n">high</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">low</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">elif</span> <span class="n">mid</span> <span class="o">&lt;</span> <span class="n">high</span><span class="p">:</span>
            <span class="c1"># 右边有序，先判断是否在右边
</span>            <span class="k">if</span> <span class="n">nums</span><span class="p">[</span><span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">target</span> <span class="ow">and</span> <span class="n">target</span> <span class="o">&lt;=</span> <span class="n">nums</span><span class="p">[</span><span class="n">high</span><span class="p">]:</span>
                <span class="n">low</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">high</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">-</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/find-k-closest-elements/">658. 找到 K 个最接近的元素</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定一个排序好的数组 `arr`，两个整数 `k` 和 `x`，从数组中找到最靠近 `x` 的 `k` 个数。返回的结果必须要是按升序排好的。
🧪样例：输入：`arr = [1,2,3,4,5]`, `k = 4`, `x = 3`；输出：`[1,2,3,4]`。
💡重点：
1. 反向思维，删除最边缘的`n - k`个，每次判断删最左边还是删最右边。
2. 返回结果要排好序，可以用双指针寻找最优子区间。
</span><span class="sh">"""</span>

<span class="k">def</span> <span class="nf">findClosestElements</span><span class="p">(</span><span class="n">arr</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">k</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
    <span class="c1"># 排除法（双指针）
</span>    <span class="n">N</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span>
    <span class="n">remove_nums</span> <span class="o">=</span> <span class="n">N</span> <span class="o">-</span> <span class="n">k</span>
    <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">N</span> <span class="o">-</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">remove_nums</span><span class="p">:</span>
        <span class="c1"># 注意：这里等于号的含义，题目中说，差值相等的时候取小的
</span>        <span class="c1"># 因此相等的时候，尽量缩小右边界
</span>        <span class="k">if</span> <span class="n">x</span> <span class="o">-</span> <span class="n">arr</span><span class="p">[</span><span class="n">left</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">arr</span><span class="p">[</span><span class="n">right</span><span class="p">]</span> <span class="o">-</span> <span class="n">x</span><span class="p">:</span>
            <span class="n">right</span> <span class="o">-=</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">left</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">remove_nums</span> <span class="o">-=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">arr</span><span class="p">[</span><span class="n">left</span><span class="p">:</span><span class="n">left</span> <span class="o">+</span> <span class="n">k</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/kth-largest-element-in-an-array">215. 数组中的第K个最大元素</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：
    给定整数数组 nums 和整数 k，请返回数组中第 k 个最大的元素。
    请注意，你需要找的是数组排序后的第 k 个最大的元素，而不是第 k 个不同的元素。
    你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
🧪样例：
    输入: [3,2,1,5,6,4], k = 2
    输出: 5

    输入: [3,2,3,1,2,4,5,5,6], k = 4
    输出: 4
💡重点：
</span><span class="sh">"""</span>
<span class="k">class</span> <span class="nc">Solution</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">partition</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">left</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
        <span class="sh">"""</span><span class="s">
        在子数组 [left, right] 中随机选择一个基准元素 pivot
        根据 pivot 重新排列子数组 [left, right]
        重新排列后，&lt;= pivot 的元素都在 pivot 的左侧，&gt;= pivot 的元素都在 pivot 的右侧
        返回 pivot 在重新排列后的 nums 中的下标
        特别地，如果子数组的所有元素都等于 pivot，我们会返回子数组的中心下标，避免退化
        </span><span class="sh">"""</span>

        <span class="c1"># 1. 在子数组 [left, right] 中随机选择一个基准元素 pivot
</span>        <span class="n">i</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="nf">randint</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>
        <span class="n">pivot</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
        <span class="c1"># 把 pivot 与子数组第一个元素交换，避免 pivot 干扰后续划分，从而简化实现逻辑
</span>        <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>

        <span class="c1"># 2. 相向双指针遍历子数组 [left + 1, right]
</span>        <span class="c1"># 循环不变量：在循环过程中，子数组的数据分布始终如下图
</span>        <span class="c1"># [ pivot | &lt;=pivot | 尚未遍历 | &gt;=pivot ]
</span>        <span class="c1">#   ^                 ^     ^         ^
</span>        <span class="c1">#   left              i     j         right
</span>
        <span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">right</span>
        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">j</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">pivot</span><span class="p">:</span>
                <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="c1"># 此时 nums[i] &gt;= pivot
</span>
            <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">j</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">pivot</span><span class="p">:</span>
                <span class="n">j</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="c1"># 此时 nums[j] &lt;= pivot
</span>
            <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">j</span><span class="p">:</span>
                <span class="k">break</span>

            <span class="c1"># 维持循环不变量
</span>            <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="n">j</span> <span class="o">-=</span> <span class="mi">1</span>

        <span class="c1"># 循环结束后
</span>        <span class="c1"># [ pivot | &lt;=pivot | &gt;=pivot ]
</span>        <span class="c1">#   ^             ^   ^     ^
</span>        <span class="c1">#   left          j   i     right
</span>
        <span class="c1"># 3. 把 pivot 与 nums[j] 交换，完成划分（partition）
</span>        <span class="c1"># 为什么与 j 交换？
</span>        <span class="c1"># 如果与 i 交换，可能会出现 i = right + 1 的情况，已经下标越界了，无法交换
</span>        <span class="c1"># 另一个原因是如果 nums[i] &gt; pivot，交换会导致一个大于 pivot 的数出现在子数组最左边，不是有效划分
</span>        <span class="c1"># 与 j 交换，即使 j = left，交换也不会出错
</span>        <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span>

        <span class="c1"># 交换后
</span>        <span class="c1"># [ &lt;=pivot | pivot | &gt;=pivot ]
</span>        <span class="c1">#               ^
</span>        <span class="c1">#               j
</span>
        <span class="c1"># 返回 pivot 的下标
</span>        <span class="k">return</span> <span class="n">j</span>

    <span class="k">def</span> <span class="nf">findKthLargest</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">nums</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">k</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
        <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>
        <span class="n">target_index</span> <span class="o">=</span> <span class="n">n</span> <span class="o">-</span> <span class="n">k</span>  <span class="c1"># 第 k 大元素在升序数组中的下标是 n - k
</span>        <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span>  <span class="c1"># 闭区间
</span>        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
            <span class="n">i</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">partition</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="n">target_index</span><span class="p">:</span>
                <span class="c1"># 找到第 k 大元素
</span>                <span class="k">return</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
            <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="n">target_index</span><span class="p">:</span>
                <span class="c1"># 第 k 大元素在 [left, i - 1] 中
</span>                <span class="n">right</span> <span class="o">=</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="c1"># 第 k 大元素在 [i + 1, right] 中
</span>                <span class="n">left</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="排序">排序</h2>

<h3 id="算法复杂度-3">算法复杂度</h3>
<p>时间复杂度：</p>
<ul>
<li>快速排序：期望 $O(N\log(N))$</li>
<li>归并排序：期望 $O(N\log(N))$</li>
</ul>
<p>空间复杂度：</p>
<ul>
<li>快速排序：期望 $O(1)$</li>
<li>归并排序：期望 $O(N)$</li>
</ul>
<h3 id="使用场景-2">使用场景</h3>
<ul>
<li>需要将数据排序后再处理（如二分查找、合并区间等）(90%)</li>
<li>快速选择/Top K问题（快速排序的变种）(80%)</li>
<li>逆序对数量统计（归并排序的变种）(90%)</li>
<li>合并多个有序数组/链表 (归并排序思想)(80%)</li>
<li>需要稳定排序时使用归并排序 (100%)</li>
</ul>
<h3 id="代码模板-2">代码模板</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="rouge-code"><pre><span class="c1"># 快速排序
</span><span class="k">def</span> <span class="nf">quick_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">left</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">原地排序，平均时间 O(NlogN)，最坏 O(N^2)，空间 O(1)</span><span class="sh">"""</span>
    <span class="k">if</span> <span class="n">left</span> <span class="o">&gt;=</span> <span class="n">right</span><span class="p">:</span>
        <span class="k">return</span>
    <span class="c1"># 随机选择 pivot 避免最坏情况
</span>    <span class="n">pivot_idx</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="nf">randint</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>
    <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">pivot_idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">pivot_idx</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span>
    <span class="n">pivot</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span>

    <span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">right</span>
    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">j</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">pivot</span><span class="p">:</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">j</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">pivot</span><span class="p">:</span>
            <span class="n">j</span> <span class="o">-=</span> <span class="mi">1</span>
        <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">j</span><span class="p">:</span>
            <span class="k">break</span>
        <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">j</span> <span class="o">-=</span> <span class="mi">1</span>
    <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span>

    <span class="nf">quick_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
    <span class="nf">quick_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>

<span class="c1"># 归并排序
</span><span class="k">def</span> <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">left</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">稳定排序，时间 O(NlogN)，空间 O(N)</span><span class="sh">"""</span>
    <span class="k">if</span> <span class="n">left</span> <span class="o">&gt;=</span> <span class="n">right</span><span class="p">:</span>
        <span class="k">return</span>
    <span class="n">mid</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="p">(</span><span class="n">right</span> <span class="o">-</span> <span class="n">left</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
    <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span><span class="p">)</span>
    <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>
    <span class="nf">merge</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">merge</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">left</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">mid</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="n">temp</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">mid</span> <span class="ow">and</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">right</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]:</span>
            <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">])</span>
            <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">mid</span><span class="p">:</span>
        <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
        <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">while</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">right</span><span class="p">:</span>
        <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">])</span>
        <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">:</span><span class="n">right</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">temp</span>

<span class="c1"># 堆排序
</span><span class="k">def</span> <span class="nf">heap_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">原地排序，时间 O(NlogN)，空间 O(1)</span><span class="sh">"""</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>
    <span class="c1"># 建堆
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span> <span class="o">//</span> <span class="mi">2</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
        <span class="nf">heapify</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
    <span class="c1"># 逐个取出堆顶
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
        <span class="n">nums</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
        <span class="nf">heapify</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">heapify</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">i</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
    <span class="n">largest</span> <span class="o">=</span> <span class="n">i</span>
    <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">2</span>
    <span class="k">if</span> <span class="n">left</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">nums</span><span class="p">[</span><span class="n">largest</span><span class="p">]:</span>
        <span class="n">largest</span> <span class="o">=</span> <span class="n">left</span>
    <span class="k">if</span> <span class="n">right</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="ow">and</span> <span class="n">nums</span><span class="p">[</span><span class="n">right</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">nums</span><span class="p">[</span><span class="n">largest</span><span class="p">]:</span>
        <span class="n">largest</span> <span class="o">=</span> <span class="n">right</span>
    <span class="k">if</span> <span class="n">largest</span> <span class="o">!=</span> <span class="n">i</span><span class="p">:</span>
        <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">largest</span><span class="p">]</span> <span class="o">=</span> <span class="n">nums</span><span class="p">[</span><span class="n">largest</span><span class="p">],</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
        <span class="nf">heapify</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">largest</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="例题-3">例题</h3>
<p><a href="https://leetcode.cn/problems/sort-list">148. 排序链表</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你链表的头结点 `head`，请将其按升序排列并返回排序后的链表。
🧪样例：
    输入：head = [4,2,1,3]
    输出：[1,2,3,4]
💡重点：
    1. 要求 O(NlogN) 时间复杂度和常数级空间复杂度
    2. 使用归并排序：找到中点 -&gt; 递归排序 -&gt; 合并
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">sortList</span><span class="p">(</span><span class="n">head</span><span class="p">:</span> <span class="n">ListNode</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ListNode</span><span class="p">:</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">head</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">head</span><span class="p">.</span><span class="nb">next</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">head</span>

    <span class="c1"># 找到中点（快慢指针）
</span>    <span class="n">slow</span><span class="p">,</span> <span class="n">fast</span> <span class="o">=</span> <span class="n">head</span><span class="p">,</span> <span class="n">head</span><span class="p">.</span><span class="nb">next</span>
    <span class="k">while</span> <span class="n">fast</span> <span class="ow">and</span> <span class="n">fast</span><span class="p">.</span><span class="nb">next</span><span class="p">:</span>
        <span class="n">slow</span> <span class="o">=</span> <span class="n">slow</span><span class="p">.</span><span class="nb">next</span>
        <span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="p">.</span><span class="nb">next</span><span class="p">.</span><span class="nb">next</span>

    <span class="c1"># 断开
</span>    <span class="n">mid</span> <span class="o">=</span> <span class="n">slow</span><span class="p">.</span><span class="nb">next</span>
    <span class="n">slow</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="bp">None</span>

    <span class="c1"># 递归排序
</span>    <span class="n">left</span> <span class="o">=</span> <span class="nf">sortList</span><span class="p">(</span><span class="n">head</span><span class="p">)</span>
    <span class="n">right</span> <span class="o">=</span> <span class="nf">sortList</span><span class="p">(</span><span class="n">mid</span><span class="p">)</span>

    <span class="c1"># 合并两个有序链表
</span>    <span class="n">dummy</span> <span class="o">=</span> <span class="nc">ListNode</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">p</span> <span class="o">=</span> <span class="n">dummy</span>
    <span class="k">while</span> <span class="n">left</span> <span class="ow">and</span> <span class="n">right</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">left</span><span class="p">.</span><span class="n">val</span> <span class="o">&lt;</span> <span class="n">right</span><span class="p">.</span><span class="n">val</span><span class="p">:</span>
            <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">left</span>
            <span class="n">left</span> <span class="o">=</span> <span class="n">left</span><span class="p">.</span><span class="nb">next</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">right</span>
            <span class="n">right</span> <span class="o">=</span> <span class="n">right</span><span class="p">.</span><span class="nb">next</span>
        <span class="n">p</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="nb">next</span>
    <span class="n">p</span><span class="p">.</span><span class="nb">next</span> <span class="o">=</span> <span class="n">left</span> <span class="k">if</span> <span class="n">left</span> <span class="k">else</span> <span class="n">right</span>
    <span class="k">return</span> <span class="n">dummy</span><span class="p">.</span><span class="nb">next</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof">剑指 Offer 51. 数组中的逆序对</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：在数组中的两个数字，如果前面一个数字大于后面的数字，则这两个数字组成一个逆序对。
    输入一个数组，求出这个数组中的逆序对的总数。
🧪样例：
    输入：[7,5,6,4]
    输出：5
    解释：逆序对 (7,5), (7,6), (7,4), (5,4), (6,4)
💡重点：
    1. 利用归并排序的过程统计逆序对
    2. 当左边元素 &gt; 右边元素时，左边剩余元素都与右边当前元素构成逆序对
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">reversePairs</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">left</span> <span class="o">&gt;=</span> <span class="n">right</span><span class="p">:</span>
            <span class="k">return</span> <span class="mi">0</span>
        <span class="n">mid</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="p">(</span><span class="n">right</span> <span class="o">-</span> <span class="n">left</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
        <span class="n">count</span> <span class="o">=</span> <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span><span class="p">)</span> <span class="o">+</span> <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>

        <span class="c1"># 合并并统计逆序对
</span>        <span class="n">temp</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="n">left</span><span class="p">,</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">mid</span> <span class="ow">and</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">right</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]:</span>
                <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
                <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">])</span>
                <span class="n">count</span> <span class="o">+=</span> <span class="n">mid</span> <span class="o">-</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>  <span class="c1"># 关键：左边剩余元素都与 nums[j] 构成逆序对
</span>                <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">mid</span><span class="p">:</span>
            <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">right</span><span class="p">:</span>
            <span class="n">temp</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">])</span>
            <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">nums</span><span class="p">[</span><span class="n">left</span><span class="p">:</span><span class="n">right</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">temp</span>
        <span class="k">return</span> <span class="n">count</span>

    <span class="k">return</span> <span class="nf">merge_sort</span><span class="p">(</span><span class="n">nums</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="动态规划">动态规划</h2>

<p>动态规划四要素：</p>
<ul>
<li>状态 (State) – 递归的定义</li>
<li>方程 (Function) – 递归的拆解</li>
<li>初始化 (Initialization) – 递归的出口</li>
<li>答案 (Answer) – 递归的调用</li>
</ul>
<p>常见的动态规划类型：</p>
<ul>
<li>背包型：给出 <code class="language-plaintext highlighter-rouge">n</code> 个物品及其大小，能否挑选出一些物品装满大小为 <code class="language-plaintext highlighter-rouge">m</code> 的背包
<ul>
<li>通常用二维的状态数组<code class="language-plaintext highlighter-rouge">dp[i][j]</code>，表示？？？</li>
</ul>
</li>
<li>区间型：题目中有 <code class="language-plaintext highlighter-rouge">subarray</code> / <code class="language-plaintext highlighter-rouge">substring</code> 的信息，通常大区间依赖小区间
<ul>
<li><code class="language-plaintext highlighter-rouge">dp[i][j]</code> 表示数组/字符串中 <code class="language-plaintext highlighter-rouge">i</code>, <code class="language-plaintext highlighter-rouge">j</code> 这一段区间的最优值/可行性/方案总数</li>
</ul>
</li>
<li>匹配型：通常两个字符串的匹配值依赖于两个字符串前缀的匹配值
<ul>
<li><code class="language-plaintext highlighter-rouge">dp[i][j]</code> 表示第一个字符串的前 <code class="language-plaintext highlighter-rouge">i</code> 个字符与第二个字符串的前 <code class="language-plaintext highlighter-rouge">j</code> 个字符的状态(max/min/sum/or)</li>
</ul>
</li>
<li>接龙型：给一个接龙规则，求最长的龙有多长
<ul>
<li><code class="language-plaintext highlighter-rouge">dp[i]</code> 表示以坐标为 <code class="language-plaintext highlighter-rouge">i</code> 的元素结尾的最长龙的长度</li>
</ul>
</li>
</ul>
<h3 id="算法复杂度-4">算法复杂度</h3>
<p>时间复杂度：O(状态总数 * 每个状态的处理耗费)</p>
<p>空间复杂度：O(状态总数)</p>
<h3 id="使用场景-3">使用场景</h3>
<ul>
<li>求方案总数(90%)</li>
<li>求最值(80%)</li>
<li>求可行性(80%)</li>
</ul>
<p>不适用的场景：</p>
<ul>
<li>找所有具体的方案（准确率 99%）</li>
<li>输入数据无序(除了背包问题外，准确率 60%~70%)</li>
<li>暴力算法已经是多项式时间复杂度（准确率 80%）</li>
</ul>
<h3 id="代码模板-3">代码模板</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
</pre></td><td class="rouge-code"><pre><span class="c1"># 一维动态规划模板
</span><span class="k">def</span> <span class="nf">dp_1d</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="c1"># 1. 定义状态数组
</span>    <span class="n">dp</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="c1"># 2. 初始化
</span>    <span class="n">dp</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">base_case</span>
    <span class="c1"># 3. 状态转移
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">状态转移方程</span>
    <span class="c1"># 4. 返回答案
</span>    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="n">n</span><span class="p">]</span>

<span class="c1"># 二维动态规划模板
</span><span class="k">def</span> <span class="nf">dp_2d</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
    <span class="c1"># 1. 定义状态数组
</span>    <span class="n">dp</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">m</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)]</span>
    <span class="c1"># 2. 初始化
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">m</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="bp">...</span>
    <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">dp</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">...</span>
    <span class="c1"># 3. 状态转移
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">m</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
            <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">状态转移方程</span>
    <span class="c1"># 4. 返回答案
</span>    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="n">m</span><span class="p">][</span><span class="n">n</span><span class="p">]</span>

<span class="c1"># 背包问题模板（0-1背包）
</span><span class="k">def</span> <span class="nf">knapsack</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">W</span><span class="p">,</span> <span class="n">weights</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
    <span class="c1"># dp[i][j] 表示前i个物品，容量为j时的最大价值
</span>    <span class="n">dp</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">W</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">W</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">j</span> <span class="o">&gt;=</span> <span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">]:</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">],</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">]]</span> <span class="o">+</span> <span class="n">values</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="n">n</span><span class="p">][</span><span class="n">W</span><span class="p">]</span>

<span class="c1"># 空间优化版本（一维数组）
</span><span class="k">def</span> <span class="nf">knapsack_optimized</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">W</span><span class="p">,</span> <span class="n">weights</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
    <span class="n">dp</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">W</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">W</span><span class="p">,</span> <span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>  <span class="c1"># 逆序遍历
</span>            <span class="n">dp</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="n">dp</span><span class="p">[</span><span class="n">j</span> <span class="o">-</span> <span class="n">weights</span><span class="p">[</span><span class="n">i</span><span class="p">]]</span> <span class="o">+</span> <span class="n">values</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="n">W</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="例题-4">例题</h3>
<p><a href="https://leetcode.cn/problems/coin-change">322. 零钱兑换</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个整数数组 `coins`，表示不同面额的硬币；以及一个整数 `amount`，表示总金额。
    计算并返回可以凑成总金额所需的**最少**的硬币个数。如果没有任何一种硬币组合能组成总金额，返回 `-1`。
🧪样例：
    输入：coins = [1, 2, 5], amount = 11
    输出：3
    解释：11 = 5 + 5 + 1
💡重点：
    1. 完全背包问题，每种硬币可以使用多次
    2. dp[i] 表示凑成金额 i 需要的最少硬币数
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">coinChange</span><span class="p">(</span><span class="n">coins</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">amount</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">dp</span> <span class="o">=</span> <span class="p">[</span><span class="nf">float</span><span class="p">(</span><span class="sh">'</span><span class="s">inf</span><span class="sh">'</span><span class="p">)]</span> <span class="o">*</span> <span class="p">(</span><span class="n">amount</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">dp</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">amount</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">coin</span> <span class="ow">in</span> <span class="n">coins</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">coin</span><span class="p">:</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="n">coin</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="n">amount</span><span class="p">]</span> <span class="k">if</span> <span class="n">dp</span><span class="p">[</span><span class="n">amount</span><span class="p">]</span> <span class="o">!=</span> <span class="nf">float</span><span class="p">(</span><span class="sh">'</span><span class="s">inf</span><span class="sh">'</span><span class="p">)</span> <span class="k">else</span> <span class="o">-</span><span class="mi">1</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/longest-increasing-subsequence">300. 最长递增子序列</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个整数数组 `nums`，找到其中最长严格递增子序列的长度。
🧪样例：
    输入：nums = [10,9,2,5,3,7,101,18]
    输出：4
    解释：最长递增子序列是 [2,3,7,101]，长度为 4
💡重点：
    1. dp[i] 表示以 nums[i] 结尾的最长递增子序列长度
    2. 时间 O(N^2)，可以用二分优化到 O(NlogN)
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">lengthOfLIS</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span>
    <span class="n">dp</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="n">n</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">i</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">nums</span><span class="p">[</span><span class="n">j</span><span class="p">]:</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">dp</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="k">return</span> <span class="nf">max</span><span class="p">(</span><span class="n">dp</span><span class="p">)</span>

<span class="c1"># 二分优化版本 O(NlogN)
</span><span class="k">def</span> <span class="nf">lengthOfLIS</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">tails</span> <span class="o">=</span> <span class="p">[]</span>  <span class="c1"># tails[i] 表示长度为 i+1 的子序列的最小末尾
</span>    <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">nums</span><span class="p">:</span>
        <span class="c1"># 二分查找第一个 &gt;= num 的位置
</span>        <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">tails</span><span class="p">)</span>
        <span class="k">while</span> <span class="n">left</span> <span class="o">&lt;</span> <span class="n">right</span><span class="p">:</span>
            <span class="n">mid</span> <span class="o">=</span> <span class="n">left</span> <span class="o">+</span> <span class="p">(</span><span class="n">right</span> <span class="o">-</span> <span class="n">left</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
            <span class="k">if</span> <span class="n">tails</span><span class="p">[</span><span class="n">mid</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">num</span><span class="p">:</span>
                <span class="n">left</span> <span class="o">=</span> <span class="n">mid</span> <span class="o">+</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">right</span> <span class="o">=</span> <span class="n">mid</span>
        <span class="k">if</span> <span class="n">left</span> <span class="o">==</span> <span class="nf">len</span><span class="p">(</span><span class="n">tails</span><span class="p">):</span>
            <span class="n">tails</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">num</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">tails</span><span class="p">[</span><span class="n">left</span><span class="p">]</span> <span class="o">=</span> <span class="n">num</span>
    <span class="k">return</span> <span class="nf">len</span><span class="p">(</span><span class="n">tails</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/longest-common-subsequence">1143. 最长公共子序列</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定两个字符串 `text1` 和 `text2`，返回这两个字符串的最长公共子序列的长度。
🧪样例：
    输入：text1 = </span><span class="sh">"</span><span class="s">abcde</span><span class="sh">"</span><span class="s">, text2 = </span><span class="sh">"</span><span class="s">ace</span><span class="sh">"</span><span class="s">
    输出：3
    解释：最长公共子序列是 </span><span class="sh">"</span><span class="s">ace</span><span class="sh">"</span><span class="s">
💡重点：
    1. dp[i][j] 表示 text1 前 i 个字符和 text2 前 j 个字符的 LCS 长度
    2. 匹配型动态规划
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">longestCommonSubsequence</span><span class="p">(</span><span class="n">text1</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">text2</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">m</span><span class="p">,</span> <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">text1</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">text2</span><span class="p">)</span>
    <span class="n">dp</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">m</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">m</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">text1</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="n">text2</span><span class="p">[</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]:</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">],</span> <span class="n">dp</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="n">m</span><span class="p">][</span><span class="n">n</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="贪心算法">贪心算法</h2>

<p>贪心算法是一种在每一步选择中都采取在当前状态下最好/最优的选择，从而希望导致结果是全局最好/最优的算法。</p>
<h3 id="使用场景-4">使用场景</h3>
<ul>
<li>区间调度问题（按结束时间排序）(90%)</li>
<li>跳跃游戏类问题 (80%)</li>
<li>分发糖果/分配问题 (70%)</li>
<li>股票买卖问题（只能买卖一次或多次）(80%)</li>
<li>Huffman编码 (100%)</li>
</ul>
<h3 id="例题-5">例题</h3>
<p><a href="https://leetcode.cn/problems/jump-game">55. 跳跃游戏</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个非负整数数组 `nums`，你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。
    判断你是否能够到达最后一个下标。
🧪样例：
    输入：nums = [2,3,1,1,4]
    输出：true
    解释：可以先跳 1 步，从下标 0 到达下标 1，然后再从下标 1 跳 3 步到达最后一个下标。
💡重点：
    1. 贪心维护最远可达位置
    2. 如果当前位置超过了最远可达位置，则无法继续
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">canJump</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="n">max_reach</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">jump</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">nums</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="n">max_reach</span><span class="p">:</span>  <span class="c1"># 当前位置不可达
</span>            <span class="k">return</span> <span class="bp">False</span>
        <span class="n">max_reach</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">max_reach</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="n">jump</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">max_reach</span> <span class="o">&gt;=</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">True</span>
    <span class="k">return</span> <span class="bp">True</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/non-overlapping-intervals">435. 无重叠区间</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定一个区间的集合，找到需要移除区间的最小数量，使剩余区间互不重叠。
🧪样例：
    输入：intervals = [[1,2],[2,3],[3,4],[1,3]]
    输出：1
    解释：移除 [1,3] 后，剩下的区间没有重叠。
💡重点：
    1. 按结束时间排序，贪心选择结束最早的区间
    2. 等价于求最多能保留多少个不重叠区间
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">eraseOverlapIntervals</span><span class="p">(</span><span class="n">intervals</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">intervals</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">0</span>
    <span class="n">intervals</span><span class="p">.</span><span class="nf">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>  <span class="c1"># 按结束时间排序
</span>    <span class="n">count</span> <span class="o">=</span> <span class="mi">1</span>  <span class="c1"># 保留的区间数
</span>    <span class="n">end</span> <span class="o">=</span> <span class="n">intervals</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">intervals</span><span class="p">)):</span>
        <span class="k">if</span> <span class="n">intervals</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="n">end</span><span class="p">:</span>  <span class="c1"># 不重叠
</span>            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="n">end</span> <span class="o">=</span> <span class="n">intervals</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span>
    <span class="k">return</span> <span class="nf">len</span><span class="p">(</span><span class="n">intervals</span><span class="p">)</span> <span class="o">-</span> <span class="n">count</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/assign-cookies">455. 分发饼干</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：假设你是一位很棒的家长，想要给你的孩子们一些小饼干。但是，每个孩子最多只能给一块饼干。
    对每个孩子 `i`，都有一个胃口值 `g[i]`，每块饼干 `j`，都有一个尺寸 `s[j]`。
    只有当 `s[j] &gt;= g[i]` 时，我们才可以将这个饼干 `j` 分配给孩子 `i`。
    目标是尽可能满足越多数量的孩子。
🧪样例：
    输入：g = [1,2,3], s = [1,1]
    输出：1
    解释：你有三个孩子和两块小饼干，3 个孩子的胃口值分别是：1, 2, 3。虽然你有两块小饼干，但只能满足胃口值为 1 的孩子。
💡重点：
    1. 排序后双指针贪心匹配
    2. 小饼干优先满足小胃口的孩子
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">findContentChildren</span><span class="p">(</span><span class="n">g</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">s</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="n">g</span><span class="p">.</span><span class="nf">sort</span><span class="p">()</span>
    <span class="n">s</span><span class="p">.</span><span class="nf">sort</span><span class="p">()</span>
    <span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="nf">len</span><span class="p">(</span><span class="n">g</span><span class="p">)</span> <span class="ow">and</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">j</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="n">g</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span>
            <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>  <span class="c1"># 满足一个孩子
</span>        <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span>  <span class="c1"># 尝试下一块饼干
</span>    <span class="k">return</span> <span class="n">i</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="宽度优先搜索-bfs">宽度优先搜索 BFS</h2>

<h3 id="算法复杂度-5">算法复杂度</h3>
<p>时间复杂度：$O(n + m)$, <code class="language-plaintext highlighter-rouge">n</code> 是点数, <code class="language-plaintext highlighter-rouge">m</code> 是边数</p>
<p>空间复杂度：$O(n)$</p>
<h3 id="使用场景-5">使用场景</h3>
<ul>
<li>拓扑排序(100%)</li>
<li>出现连通块的关键词(100%)</li>
<li>分层遍历(100%)</li>
<li>简单图最短路径(100%)</li>
<li>给定一个变换规则，从初始状态变到终止状态最少几步(100%)</li>
</ul>
<h3 id="代码模板-4">代码模板</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">deque</span>

<span class="c1"># 基本BFS模板
</span><span class="k">def</span> <span class="nf">bfs</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
    <span class="n">queue</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">([</span><span class="n">start</span><span class="p">])</span>
    <span class="n">visited</span> <span class="o">=</span> <span class="nf">set</span><span class="p">([</span><span class="n">start</span><span class="p">])</span>
    <span class="n">step</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">while</span> <span class="n">queue</span><span class="p">:</span>
        <span class="n">size</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>  <span class="c1"># 分层遍历
</span>            <span class="n">cur</span> <span class="o">=</span> <span class="n">queue</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
            <span class="k">if</span> <span class="n">cur</span> <span class="o">==</span> <span class="n">target</span><span class="p">:</span>
                <span class="k">return</span> <span class="n">step</span>
            <span class="k">for</span> <span class="n">next_node</span> <span class="ow">in</span> <span class="nf">get_neighbors</span><span class="p">(</span><span class="n">cur</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">next_node</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">visited</span><span class="p">:</span>
                    <span class="n">visited</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">next_node</span><span class="p">)</span>
                    <span class="n">queue</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">next_node</span><span class="p">)</span>
        <span class="n">step</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>

<span class="c1"># 网格BFS模板
</span><span class="k">def</span> <span class="nf">bfs_grid</span><span class="p">(</span><span class="n">grid</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">):</span>
    <span class="n">m</span><span class="p">,</span> <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">grid</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">grid</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    <span class="n">queue</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">([(</span><span class="n">start</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">start</span><span class="p">[</span><span class="mi">1</span><span class="p">])])</span>
    <span class="n">visited</span> <span class="o">=</span> <span class="nf">set</span><span class="p">([(</span><span class="n">start</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">start</span><span class="p">[</span><span class="mi">1</span><span class="p">])])</span>
    <span class="n">directions</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
    <span class="n">step</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">while</span> <span class="n">queue</span><span class="p">:</span>
        <span class="n">size</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
            <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">queue</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
            <span class="nf">if </span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="o">==</span> <span class="n">end</span><span class="p">:</span>
                <span class="k">return</span> <span class="n">step</span>
            <span class="k">for</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span> <span class="ow">in</span> <span class="n">directions</span><span class="p">:</span>
                <span class="n">nx</span><span class="p">,</span> <span class="n">ny</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">dx</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="n">dy</span>
                <span class="k">if</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">nx</span> <span class="o">&lt;</span> <span class="n">m</span> <span class="ow">and</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">ny</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="ow">and</span> <span class="p">(</span><span class="n">nx</span><span class="p">,</span> <span class="n">ny</span><span class="p">)</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">visited</span> <span class="ow">and</span> <span class="n">grid</span><span class="p">[</span><span class="n">nx</span><span class="p">][</span><span class="n">ny</span><span class="p">]</span> <span class="o">!=</span> <span class="sh">'</span><span class="s">#</span><span class="sh">'</span><span class="p">:</span>
                    <span class="n">visited</span><span class="p">.</span><span class="nf">add</span><span class="p">((</span><span class="n">nx</span><span class="p">,</span> <span class="n">ny</span><span class="p">))</span>
                    <span class="n">queue</span><span class="p">.</span><span class="nf">append</span><span class="p">((</span><span class="n">nx</span><span class="p">,</span> <span class="n">ny</span><span class="p">))</span>
        <span class="n">step</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">1</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="例题-6">例题</h3>
<p><a href="https://leetcode.cn/problems/binary-tree-level-order-traversal">102. 二叉树的层序遍历</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你二叉树的根节点 `root`，返回其节点值的层序遍历（即逐层地，从左到右访问所有节点）。
🧪样例：
    输入：root = [3,9,20,null,null,15,7]
    输出：[[3],[9,20],[15,7]]
💡重点：
    1. 使用队列进行 BFS
    2. 需要记录每层的节点数量
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">levelOrder</span><span class="p">(</span><span class="n">root</span><span class="p">:</span> <span class="n">TreeNode</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]]:</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">root</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">[]</span>
    <span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">deque</span>
    <span class="n">queue</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">([</span><span class="n">root</span><span class="p">])</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">while</span> <span class="n">queue</span><span class="p">:</span>
        <span class="n">level</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="n">size</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
            <span class="n">node</span> <span class="o">=</span> <span class="n">queue</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
            <span class="n">level</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">val</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">node</span><span class="p">.</span><span class="n">left</span><span class="p">:</span>
                <span class="n">queue</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">left</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">node</span><span class="p">.</span><span class="n">right</span><span class="p">:</span>
                <span class="n">queue</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">right</span><span class="p">)</span>
        <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">level</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">result</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/word-ladder">127. 单词接龙</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定两个单词 `beginWord` 和 `endWord`，以及一个字典 `wordList`，
    找到从 `beginWord` 到 `endWord` 的最短转换序列的长度。
    转换规则：每次只能改变一个字母。
🧪样例：
    输入：beginWord = </span><span class="sh">"</span><span class="s">hit</span><span class="sh">"</span><span class="s">, endWord = </span><span class="sh">"</span><span class="s">cog</span><span class="sh">"</span><span class="s">, wordList = [</span><span class="sh">"</span><span class="s">hot</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">dot</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">dog</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">lot</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">log</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">cog</span><span class="sh">"</span><span class="s">]
    输出：5
    解释：</span><span class="sh">"</span><span class="s">hit</span><span class="sh">"</span><span class="s"> -&gt; </span><span class="sh">"</span><span class="s">hot</span><span class="sh">"</span><span class="s"> -&gt; </span><span class="sh">"</span><span class="s">dot</span><span class="sh">"</span><span class="s"> -&gt; </span><span class="sh">"</span><span class="s">dog</span><span class="sh">"</span><span class="s"> -&gt; </span><span class="sh">"</span><span class="s">cog</span><span class="sh">"</span><span class="s">
💡重点：
    1. BFS求最短路径
    2. 双向BFS可以优化效率
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">ladderLength</span><span class="p">(</span><span class="n">beginWord</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">endWord</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">wordList</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">endWord</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">wordList</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">0</span>

    <span class="n">word_set</span> <span class="o">=</span> <span class="nf">set</span><span class="p">(</span><span class="n">wordList</span><span class="p">)</span>
    <span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">deque</span>
    <span class="n">queue</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">([(</span><span class="n">beginWord</span><span class="p">,</span> <span class="mi">1</span><span class="p">)])</span>
    <span class="n">visited</span> <span class="o">=</span> <span class="nf">set</span><span class="p">([</span><span class="n">beginWord</span><span class="p">])</span>

    <span class="k">while</span> <span class="n">queue</span><span class="p">:</span>
        <span class="n">word</span><span class="p">,</span> <span class="n">level</span> <span class="o">=</span> <span class="n">queue</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">word</span> <span class="o">==</span> <span class="n">endWord</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">level</span>
        <span class="c1"># 尝试所有可能的变换
</span>        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">word</span><span class="p">)):</span>
            <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="sh">'</span><span class="s">abcdefghijklmnopqrstuvwxyz</span><span class="sh">'</span><span class="p">:</span>
                <span class="n">new_word</span> <span class="o">=</span> <span class="n">word</span><span class="p">[:</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">c</span> <span class="o">+</span> <span class="n">word</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">:]</span>
                <span class="k">if</span> <span class="n">new_word</span> <span class="ow">in</span> <span class="n">word_set</span> <span class="ow">and</span> <span class="n">new_word</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">visited</span><span class="p">:</span>
                    <span class="n">visited</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">new_word</span><span class="p">)</span>
                    <span class="n">queue</span><span class="p">.</span><span class="nf">append</span><span class="p">((</span><span class="n">new_word</span><span class="p">,</span> <span class="n">level</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span>
    <span class="k">return</span> <span class="mi">0</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/number-of-islands">200. 岛屿数量</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个由 </span><span class="sh">'</span><span class="s">1</span><span class="sh">'</span><span class="s">（陆地）和 </span><span class="sh">'</span><span class="s">0</span><span class="sh">'</span><span class="s">（水）组成的的二维网格，请你计算网格中岛屿的数量。
🧪样例：
    输入：grid = [
      [</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">],
      [</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">],
      [</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">1</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">],
      [</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">0</span><span class="sh">"</span><span class="s">]
    ]
    输出：1
💡重点：
    1. 遍历网格，遇到 </span><span class="sh">'</span><span class="s">1</span><span class="sh">'</span><span class="s"> 就 BFS/DFS 标记整个岛屿
    2. 标记过的格子改为 </span><span class="sh">'</span><span class="s">0</span><span class="sh">'</span><span class="s"> 避免重复访问
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">numIslands</span><span class="p">(</span><span class="n">grid</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">grid</span><span class="p">:</span>
        <span class="k">return</span> <span class="mi">0</span>
    <span class="n">m</span><span class="p">,</span> <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">grid</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">grid</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">def</span> <span class="nf">bfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">):</span>
        <span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">deque</span>
        <span class="n">queue</span> <span class="o">=</span> <span class="nf">deque</span><span class="p">([(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)])</span>
        <span class="n">grid</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="sh">'</span><span class="s">0</span><span class="sh">'</span>
        <span class="n">directions</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
        <span class="k">while</span> <span class="n">queue</span><span class="p">:</span>
            <span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">queue</span><span class="p">.</span><span class="nf">popleft</span><span class="p">()</span>
            <span class="k">for</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span> <span class="ow">in</span> <span class="n">directions</span><span class="p">:</span>
                <span class="n">nx</span><span class="p">,</span> <span class="n">ny</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">dx</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="n">dy</span>
                <span class="k">if</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">nx</span> <span class="o">&lt;</span> <span class="n">m</span> <span class="ow">and</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">ny</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="ow">and</span> <span class="n">grid</span><span class="p">[</span><span class="n">nx</span><span class="p">][</span><span class="n">ny</span><span class="p">]</span> <span class="o">==</span> <span class="sh">'</span><span class="s">1</span><span class="sh">'</span><span class="p">:</span>
                    <span class="n">grid</span><span class="p">[</span><span class="n">nx</span><span class="p">][</span><span class="n">ny</span><span class="p">]</span> <span class="o">=</span> <span class="sh">'</span><span class="s">0</span><span class="sh">'</span>
                    <span class="n">queue</span><span class="p">.</span><span class="nf">append</span><span class="p">((</span><span class="n">nx</span><span class="p">,</span> <span class="n">ny</span><span class="p">))</span>

    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">m</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">grid</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">==</span> <span class="sh">'</span><span class="s">1</span><span class="sh">'</span><span class="p">:</span>
                <span class="nf">bfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>
                <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">count</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="深度优先搜索-dfs">深度优先搜索 DFS</h2>

<p>DFS使用递归或栈实现，沿着一条路径走到底再回溯，常用于遍历所有方案。</p>
<h3 id="算法复杂度-6">算法复杂度</h3>
<p>时间复杂度：O(方案个数 * 构造每个方案的时间)</p>
<ul>
<li>树的遍历： $O(N)$</li>
<li>排列问题： $O(N! * N)$</li>
<li>组合问题： $O(2^N * N)$</li>
</ul>
<h3 id="使用场景-6">使用场景</h3>
<ul>
<li>找满足某个条件的所有方案 (99%)</li>
<li>二叉树 Binary Tree 的问题 (90%)</li>
<li>组合问题(95%)
<ul>
<li>问题模型：求出所有满足条件的”组合”</li>
<li>判断条件：组合中的元素是顺序无关的</li>
</ul>
</li>
<li>排列问题 (95%)
<ul>
<li>问题模型：求出所有满足条件的”排列”</li>
<li>判断条件：组合中的元素是顺序”相关”的。</li>
</ul>
</li>
</ul>
<p>不要用 DFS 的场景:</p>
<ul>
<li>连通块问题（一定要用 BFS，否则 StackOverflow）</li>
<li>拓扑排序（一定要用 BFS，否则 StackOverflow）</li>
<li>一切 BFS 可以解决的问题</li>
</ul>
<h3 id="代码模板-5">代码模板</h3>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
</pre></td><td class="rouge-code"><pre><span class="c1"># 基本DFS模板（回溯）
</span><span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">dfs</span><span class="p">(</span><span class="n">参数列表</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">递归出口</span><span class="p">:</span>
        <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">当前方案</span><span class="p">)</span>
        <span class="k">return</span>
    <span class="k">for</span> <span class="n">所有的拆解可能性</span><span class="p">:</span>
        <span class="n">做选择</span>
        <span class="nf">dfs</span><span class="p">(</span><span class="n">参数列表</span><span class="p">)</span>
        <span class="n">撤销选择</span>

<span class="c1"># 组合问题模板
</span><span class="k">def</span> <span class="nf">combine</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">k</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]]:</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">==</span> <span class="n">k</span><span class="p">:</span>
            <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">path</span><span class="p">[:])</span>
            <span class="k">return</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
            <span class="nf">backtrack</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>
    <span class="nf">backtrack</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">[])</span>
    <span class="k">return</span> <span class="n">result</span>

<span class="c1"># 排列问题模板
</span><span class="k">def</span> <span class="nf">permute</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]]:</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">used</span><span class="p">):</span>
        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">==</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">):</span>
            <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">path</span><span class="p">[:])</span>
            <span class="k">return</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)):</span>
            <span class="k">if</span> <span class="n">used</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span>
                <span class="k">continue</span>
            <span class="n">used</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
            <span class="nf">backtrack</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">used</span><span class="p">)</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>
            <span class="n">used</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="nf">backtrack</span><span class="p">([],</span> <span class="p">[</span><span class="bp">False</span><span class="p">]</span> <span class="o">*</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">result</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="例题-7">例题</h3>
<p><a href="https://leetcode.cn/problems/permutations">46. 全排列</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定一个不含重复数字的数组 `nums`，返回其所有可能的全排列。
🧪样例：
    输入：nums = [1,2,3]
    输出：[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
💡重点：
    1. 排列问题，需要使用 used 数组记录已使用的元素
    2. 回溯时记得撤销选择
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">permute</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]]:</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">used</span><span class="p">):</span>
        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">==</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">):</span>
            <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">path</span><span class="p">[:])</span>
            <span class="k">return</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)):</span>
            <span class="k">if</span> <span class="n">used</span><span class="p">[</span><span class="n">i</span><span class="p">]:</span>
                <span class="k">continue</span>
            <span class="n">used</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
            <span class="nf">backtrack</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">used</span><span class="p">)</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>
            <span class="n">used</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span>

    <span class="nf">backtrack</span><span class="p">([],</span> <span class="p">[</span><span class="bp">False</span><span class="p">]</span> <span class="o">*</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">result</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/subsets">78. 子集</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给你一个整数数组 `nums`，数组中的元素互不相同。返回该数组所有可能的子集（幂集）。
🧪样例：
    输入：nums = [1,2,3]
    输出：[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
💡重点：
    1. 组合问题，每个元素选或不选
    2. 每个节点都是答案的一部分
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">subsets</span><span class="p">(</span><span class="n">nums</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]]:</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
        <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">path</span><span class="p">[:])</span>  <span class="c1"># 每个节点都是一个子集
</span>        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">nums</span><span class="p">)):</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">nums</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
            <span class="nf">backtrack</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
            <span class="n">path</span><span class="p">.</span><span class="nf">pop</span><span class="p">()</span>

    <span class="nf">backtrack</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="p">[])</span>
    <span class="k">return</span> <span class="n">result</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/generate-parentheses">22. 括号生成</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：数字 `n` 代表生成括号的对数，请你设计一个函数，用于能够生成所有可能的并且有效的括号组合。
🧪样例：
    输入：n = 3
    输出：[</span><span class="sh">"</span><span class="s">((()))</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">(()())</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">(())()</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">()(())</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">()()()</span><span class="sh">"</span><span class="s">]
💡重点：
    1. 左括号数量必须小于 n，右括号数量必须小于左括号数量
    2. 剪枝优化：不满足条件提前终止
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">generateParenthesis</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">n</span><span class="p">:</span>
            <span class="n">result</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
            <span class="k">return</span>
        <span class="k">if</span> <span class="n">left</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">:</span>
            <span class="nf">backtrack</span><span class="p">(</span><span class="n">s</span> <span class="o">+</span> <span class="sh">'</span><span class="s">(</span><span class="sh">'</span><span class="p">,</span> <span class="n">left</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">right</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">right</span> <span class="o">&lt;</span> <span class="n">left</span><span class="p">:</span>
            <span class="nf">backtrack</span><span class="p">(</span><span class="n">s</span> <span class="o">+</span> <span class="sh">'</span><span class="s">)</span><span class="sh">'</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>

    <span class="nf">backtrack</span><span class="p">(</span><span class="sh">''</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">result</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p><a href="https://leetcode.cn/problems/word-search">79. 单词搜索</a></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s">
📖描述：给定一个 `m x n` 二维字符网格 `board` 和一个字符串单词 `word`。
    如果 `word` 存在于网格中，返回 `true`；否则，返回 `false`。
🧪样例：
    输入：board = [[</span><span class="sh">"</span><span class="s">A</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">B</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">C</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">E</span><span class="sh">"</span><span class="s">],[</span><span class="sh">"</span><span class="s">S</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">F</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">C</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">S</span><span class="sh">"</span><span class="s">],[</span><span class="sh">"</span><span class="s">A</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">D</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">E</span><span class="sh">"</span><span class="s">,</span><span class="sh">"</span><span class="s">E</span><span class="sh">"</span><span class="s">]], word = </span><span class="sh">"</span><span class="s">ABCCED</span><span class="sh">"</span><span class="s">
    输出：true
💡重点：
    1. DFS + 回溯，注意标记已访问的格子
    2. 找到一条路径即可返回 true
</span><span class="sh">"""</span>
<span class="k">def</span> <span class="nf">exist</span><span class="p">(</span><span class="n">board</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]],</span> <span class="n">word</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
    <span class="n">m</span><span class="p">,</span> <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">board</span><span class="p">),</span> <span class="nf">len</span><span class="p">(</span><span class="n">board</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

    <span class="k">def</span> <span class="nf">dfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">,</span> <span class="n">k</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">k</span> <span class="o">==</span> <span class="nf">len</span><span class="p">(</span><span class="n">word</span><span class="p">):</span>
            <span class="k">return</span> <span class="bp">True</span>
        <span class="k">if</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="n">m</span> <span class="ow">or</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">j</span> <span class="o">&gt;=</span> <span class="n">n</span> <span class="ow">or</span> <span class="n">board</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">!=</span> <span class="n">word</span><span class="p">[</span><span class="n">k</span><span class="p">]:</span>
            <span class="k">return</span> <span class="bp">False</span>

        <span class="n">temp</span> <span class="o">=</span> <span class="n">board</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
        <span class="n">board</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="sh">'</span><span class="s">#</span><span class="sh">'</span>  <span class="c1"># 标记已访问
</span>        <span class="n">found</span> <span class="o">=</span> <span class="p">(</span><span class="nf">dfs</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">j</span><span class="p">,</span> <span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="ow">or</span> <span class="nf">dfs</span><span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">j</span><span class="p">,</span> <span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="ow">or</span>
                 <span class="nf">dfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="ow">or</span> <span class="nf">dfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="p">))</span>
        <span class="n">board</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">temp</span>  <span class="c1"># 恢复
</span>        <span class="k">return</span> <span class="n">found</span>

    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">m</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
            <span class="k">if</span> <span class="nf">dfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">,</span> <span class="mi">0</span><span class="p">):</span>
                <span class="k">return</span> <span class="bp">True</span>
    <span class="k">return</span> <span class="bp">False</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="参考资源">参考资源</h2>
<ul>
<li><a href="https://github.com/ninechapter-algorithm/leetcode-linghu-templete">北大令狐冲的算法小抄</a></li>
<li><a href="https://github.com/keon/algorithms">Algorithms</a></li>
<li><a href="https://labuladong.online/algo">labuladong的算法笔记</a></li>
<li><a href="https://greyireland.gitbook.io/algorithm-pattern">算法模板</a></li>
</ul>]]></content><author><name>Jinchao Li</name></author><category term="Study"/><category term="Algorithm"/><category term="algorithm"/><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Hello, Notion!</title><link href="https://jinchaoli.com/blog/hello,-notion!/" rel="alternate" type="text/html" title="Hello, Notion!"/><published>2026-04-23T00:00:00+08:00</published><updated>2026-04-23T00:00:00+08:00</updated><id>https://jinchaoli.com/blog/hello,-notion!</id><content type="html" xml:base="https://jinchaoli.com/blog/hello,-notion!/"><![CDATA[<h1 id="text-and-typography">Text and Typography</h1>
<h2 id="headings">Headings</h2>
<h1 class="mt-4 mb-0" id="h1---heading">H1 - heading</h1>
<h2 id="h2---heading" data-toc-skip="" class="mt-4 mb-0">H2 - heading</h2>
<h3 id="h3---heading" data-toc-skip="" class="mt-4 mb-0">H3 - heading</h3>
<h3 id="h4---heading" data-toc-skip="" class="mt-4">H4 - heading</h3>
<h2 id="paragraph">Paragraph</h2>
<p>Quisque egestas convallis ipsum, ut sollicitudin risus tincidunt a. Maecenas interdum malesuada egestas. Duis consectetur porta risus, sit amet vulputate urna facilisis ac. Phasellus semper dui non purus ultrices sodales. Aliquam ante lorem, ornare a feugiat ac, finibus nec mauris. Vivamus ut tristique nisi. Sed vel leo vulputate, efficitur risus non, posuere mi. Nullam tincidunt bibendum rutrum. Proin commodo ornare sapien. Vivamus interdum diam sed sapien blandit, sit amet aliquam risus mattis. Nullam arcu turpis, mollis quis laoreet at, placerat id nibh. Suspendisse venenatis eros eros.</p>
<h2 id="lists">Lists</h2>
<h3 id="ordered-list">Ordered list</h3>
<ol>
<li>Firstly</li>
<li>Secondly</li>
<li>Thirdly</li>
</ol>
<h3 id="unordered-list">Unordered list</h3>
<ul>
<li>Chapter
<ul>
<li>Section
<ul>
<li>Paragraph</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="todo-list">ToDo list</h3>
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Job
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked"/>Step 1</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked"/>Step 2</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled"/>Step 3</li>
</ul>
</li>
</ul>
<h3 id="description-list">Description list</h3>
<dl>
<dt>Sun</dt>
<dd>the star around which the earth orbits</dd>
<dt>Moon</dt>
<dd>the natural satellite of the earth, visible by reflected light from the sun</dd>
</dl>
<h2 id="block-quote">Block Quote</h2>
<blockquote>
<p>This line shows the <em>block quote</em>.</p>
</blockquote>
<h2 id="prompts">Prompts</h2>
<blockquote class="prompt-tip">
<p>An example showing the <code class="language-plaintext highlighter-rouge">tip</code> type prompt.</p>
</blockquote>
<blockquote class="prompt-info">
<p>An example showing the <code class="language-plaintext highlighter-rouge">info</code> type prompt.</p>
</blockquote>
<blockquote class="prompt-warn">
<p>An example showing the <code class="language-plaintext highlighter-rouge">warn</code> type prompt.</p>
</blockquote>
<blockquote class="prompt-danger">
<p>An example showing the <code class="language-plaintext highlighter-rouge">danger</code> type prompt.</p>
</blockquote>
<h2 id="tables">Tables</h2>
<table>
<thead>
<tr>
<th>Company</th>
<th>Contact</th>
<th>Country</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alfreds Futterkiste</td>
<td>Maria Anders</td>
<td>Germany</td>
</tr>
<tr>
<td>Island Trading</td>
<td>Helen Bennett</td>
<td>UK</td>
</tr>
<tr>
<td>Magazzini Alimentari Riuniti</td>
<td>Giovanni Rovelli</td>
<td>Italy</td>
</tr>
</tbody>
</table>
<h2 id="links">Links</h2>
<p><a href="http://127.0.0.1:4000/">http://127.0.0.1:4000</a></p>
<h2 id="footnote">Footnote</h2>
<p>Click the hook will locate the footnote<sup id="fnref:footnote"><a href="#fn:footnote" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, and here is another footnote<sup id="fnref:fn-nth-2"><a href="#fn:fn-nth-2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>.</p>
<h2 id="inline-code">Inline code</h2>
<p>This is an example of <code class="language-plaintext highlighter-rouge">Inline Code</code>.</p>
<h2 id="filepath">Filepath</h2>
<p>Here is the <code class="language-plaintext filepath highlighter-rouge">/path/to/the/filename</code>.</p>
<h2 id="code-blocks">Code blocks</h2>
<p>Code blocks support syntax highlighting, one-click copying, collapsing-expanding, and dark/light theme switching.</p>
<h3 id="common">Common</h3>
<p>A plain code block with no language specified:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>This is a common code snippet, without syntax highlight and line number.
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="specific-language">Specific Language</h3>
<p>Specify the language after the opening backticks (e.g. ````bash`) to enable syntax highlighting and line numbers.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"The command was not successful."</span><span class="p">;</span>
  <span class="c">#do the needful / exit</span>
<span class="k">fi</span><span class="p">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="specific-filename">Specific filename</h3>
<p>Add <code class="language-plaintext highlighter-rouge">{: file='filename'}</code> right after the closing backticks to display a filename label on the code block.</p>
<div file="_sass/jekyll-theme-chirpy.scss" class="language-sass highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">@import</span>
  <span class="s2">"colors/light-typography"</span><span class="o">,</span>
  <span class="s2">"colors/dark-typography"</span><span class="o">;</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="jupyter-notebook">Jupyter notebook</h3>
<p>Embed a Jupyter notebook directly into the post using the <code class="language-plaintext highlighter-rouge">jupyter_notebook</code> tag.</p>
<div class="jupyter-notebook" data-light-src="/assets/jupyter/demo.ipynb.light.html" data-dark-src="/assets/jupyter/demo.ipynb.dark.html">
<div class="code-header">
<span data-label-text="Jupyter Notebook"><i class="fas fa-code fa-fw small"></i></span>
<div class="code-header-fold-trigger jupyter-fold-trigger"></div>
<div class="code-header-buttons">
<button class="fold-btn jupyter-fold-btn" data-bs-original-title="Collapse">
<i class="fas fa-chevron-up"></i>
</button>
<button class="clipboard-btn jupyter-clipboard-btn" aria-label="copy" data-title-succeed="Copied!">
<i class="far fa-clipboard"></i>
</button>
</div>
</div>
<div class="jupyter-notebook-iframe-container">
<iframe class="jupyter-iframe jupyter-iframe-light" src="/assets/jupyter/demo.ipynb.light.html" style="display:none;">
</iframe>
<iframe class="jupyter-iframe jupyter-iframe-dark" src="/assets/jupyter/demo.ipynb.dark.html" style="display:none;">
</iframe>
</div>
</div>
<h2 id="mathematics">Mathematics</h2>
<p>The mathematics powered by <strong><a href="https://www.mathjax.org/">MathJax</a></strong>:</p>
\[\begin{equation}
\sum_{n=1}^\infty 1/n^2 = \frac{\pi^2}{6}
\label{eq:series}
\end{equation}\]
<p>We can reference the equation as \eqref{eq:series}.</p>
<p>When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are</p>
\[x = {-b \pm \sqrt{b^2-4ac} \over 2a}\]
<h2 id="mermaid-svg">Mermaid SVG</h2>
<pre><code class="language-mermaid"> gantt
  title  Adding GANTT diagram functionality to mermaid
  apple :a, 2017-07-20, 1w
  banana :crit, b, 2017-07-23, 1d
  cherry :active, c, after b a, 1d
</code></pre>
<h2 id="mindmap">Mindmap</h2>
<p>View <a href="https://github.com/markmap/markmap/blob/master/packages/markmap-view/src/constants.ts">markmap.js.org</a> for configuration details.</p>
<div class="markmap">
<script type="text/template">
---
markmap:
  maxWidth: 300
  pan: false
  autoFit: true
  fitRatio: 0.7
---

- markmap
  - lib
    - Transformer
    - builtInPlugins
  - render
    - fillTemplate
  - view
    - Markmap
    - loadCSS
    - loadJS
  - autoloader
    - autoLoader
	</script>
</div>
<h2 id="images">Images</h2>
<h3 id="default-with-caption">Default (with caption)</h3>
<p><img src="https://chirpy-img.netlify.app/posts/20190808/mockup.png" alt="" width="972" height="589"/></p>
<p><em>Full screen width and center alignment</em></p>
<h3 id="left-aligned">Left aligned</h3>
<p><img src="https://chirpy-img.netlify.app/posts/20190808/mockup.png" alt="" width="972" height="589" class="w-75 normal"/></p>
<h3 id="float-to-left">Float to left</h3>
<p><img src="https://chirpy-img.netlify.app/posts/20190808/mockup.png" alt="" width="972" height="589" class="w-50 left"/><br/>
Praesent maximus aliquam sapien. Sed vel neque in dolor pulvinar auctor. Maecenas pharetra, sem sit amet interdum posuere, tellus lacus eleifend magna, ac lobortis felis ipsum id sapien. Proin ornare rutrum metus, ac convallis diam volutpat sit amet. Phasellus volutpat, elit sit amet tincidunt mollis, felis mi scelerisque mauris, ut facilisis leo magna accumsan sapien. In rutrum vehicula nisl eget tempor. Nullam maximus ullamcorper libero non maximus. Integer ultricies velit id convallis varius. Praesent eu nisl eu urna finibus ultrices id nec ex. Mauris ac mattis quam. Fusce aliquam est nec sapien bibendum, vitae malesuada ligula condimentum.</p>
<h3 id="float-to-right">Float to right</h3>
<p><img src="https://chirpy-img.netlify.app/posts/20190808/mockup.png" alt="" width="972" height="589" class="w-50 right"/><br/>
Praesent maximus aliquam sapien. Sed vel neque in dolor pulvinar auctor. Maecenas pharetra, sem sit amet interdum posuere, tellus lacus eleifend magna, ac lobortis felis ipsum id sapien. Proin ornare rutrum metus, ac convallis diam volutpat sit amet. Phasellus volutpat, elit sit amet tincidunt mollis, felis mi scelerisque mauris, ut facilisis leo magna accumsan sapien. In rutrum vehicula nisl eget tempor. Nullam maximus ullamcorper libero non maximus. Integer ultricies velit id convallis varius. Praesent eu nisl eu urna finibus ultrices id nec ex. Mauris ac mattis quam. Fusce aliquam est nec sapien bibendum, vitae malesuada ligula condimentum.</p>
<h3 id="darklight-mode--shadow">Dark/Light mode &amp; Shadow</h3>
<p>The image below will toggle dark/light mode based on theme preference, notice it has shadows.</p>
<p><img src="https://chirpy-img.netlify.app/posts/20190808/devtools-light.png" alt="" class="light w-75 shadow rounded-10" w="1212" h="668"/></p>
<p><img src="https://chirpy-img.netlify.app/posts/20190808/devtools-dark.png" alt="" class="dark w-75 shadow rounded-10" w="1212" h="668"/></p>
<h2 id="video">Video</h2>
<div class="yt-lite embed-video" data-id="Balreaj8Yqs" onclick="
var iframe=document.createElement('iframe');
iframe.setAttribute('src', 'https://www.youtube.com/embed/' + this.dataset.id + '?autoplay=1');
iframe.setAttribute('title', 'YouTube video player');
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture');
iframe.setAttribute('allowfullscreen', '');
iframe.className = 'embed-video';
this.parentNode.replaceChild(iframe, this);
">
<img src="https://i.ytimg.com/vi/Balreaj8Yqs/hqdefault.jpg" alt="YouTube video thumbnail" loading="lazy"/>
<button class="yt-play-btn" aria-label="Play video">
<svg viewBox="0 0 68 48" xmlns="http://www.w3.org/2000/svg">
<path d="M66.5 7.7c-.8-2.9-3-5.1-5.8-5.8C55.8 0 34 0 34 0S12.2 0 7.3 1.9C4.5 2.6 2.3 4.9 1.5 7.7 0 12.8 0 24 0 24s0 11.2 1.5 16.3c.8 2.9 3 5.1 5.8 5.8C12.2 48 34 48 34 48s21.8 0 26.7-1.9c2.8-.8 5-3 5.8-5.8C68 35.2 68 24 68 24s0-11.2-1.5-16.3z" fill="#f00"/>
<path d="M45 24 27 14v20" fill="#fff"/>
</svg>
</button>
</div>
<h2 id="reverse-footnote">Reverse Footnote</h2>
<p><br/></p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:footnote">
<p>The footnote source <a href="#fnref:footnote" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
</li>
<li id="fn:fn-nth-2">
<p>The 2nd footnote source <a href="#fnref:fn-nth-2" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
</li>
</ol>
</div>]]></content><author><name>Jinchao Li</name></author><category term="Blogging"/><category term="demo"/><summary type="html"><![CDATA[Examples of text, typography, math equations, diagrams, flowcharts, pictures, videos, and more using Notion.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://chirpy-img.netlify.app/commons/devices-mockup.png"/><media:content medium="image" url="https://chirpy-img.netlify.app/commons/devices-mockup.png" xmlns:media="http://search.yahoo.com/mrss/"/></entry></feed>