<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[林下]]></title><description><![CDATA[sakulyn的小站]]></description><link>https://sakulyn.top</link><image><url>https://sakulyn.top/sakura.svg</url><title>林下</title><link>https://sakulyn.top</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Sat, 27 Jun 2026 11:26:56 GMT</lastBuildDate><atom:link href="https://sakulyn.top/feed" rel="self" type="application/rss+xml"/><pubDate>Sat, 27 Jun 2026 11:26:56 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[关于我的 Mac：常用软件、桌面美学与配置艺术]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tinker/mac-setup">https://sakulyn.top/posts/tinker/mac-setup</a></blockquote><div><p>记录 —— 深度使用 Mac 五年后，我最终留下的软件与配置。</p><h2 id="">第一乐章：桌面美学</h2><p>其实也没什么桌面美学，less is more，mac 本来就很美丽了，如果是 Arch Linux，那还是有点说法，可以折腾的就多了。</p><h2 id="">第二乐章：常用软件</h2><h3 id="">效率这一块</h3><p><a href="https://apps.apple.com/cn/app/%E8%B6%85%E7%BA%A7%E5%8F%B3%E9%94%AE-irightmouse/id1497428978?mt=12">iRightMouse</a>：扩展右键设置，比如新建 txt、word、markdown 等文件，拷贝路径，剪切，进入 VSCode 等功能</p><p><a href="https://z.weixin.qq.com/">微信输入法</a>：比原生输入法更好用，在设置中勾选使用 shift 切换中英文</p><p><a href="https://apps.apple.com/sa/app/icollections-9/id6757525056?mt=12">iCollections 9</a>：桌面整理、分区管理</p><p><a href="https://github.com/jordanbaird/Ice">Ice</a>：开源菜单栏管理工具，主打一个清爽</p><p><a href="https://apps.apple.com/us/app/magnet/id441258766?mt=12">Magnet</a>：窗口管理</p><p><a href="https://www.neatdownloadmanager.com/index.php/en/">NeatDownloadManager</a>：免费小巧的多线程下载器</p><p><a href="https://github.com/exelban/stats">Stats</a>：开源性能监控工具，方便观测 CPU、GPU、内存、磁盘、网络等实时性能数据</p><h3 id="">网络与远程</h3><p><a href="https://github.com/yanue/V2rayU">V2rayU</a>：这是魔法啊🧙‍♀️，懂得都懂，快捷键设置个 cmd + u 快速切换代理开关</p><p><a href="https://tailscale.com/download">Tailscale</a>：零配置 VPN 工具</p><p><a href="https://apps.apple.com/cn/app/windows-app/id1295203466?mt=12">Windows App</a>：安全连接到 Windows 的网关，远程桌面</p><p><a href="https://www.parallels.cn/products/desktop/">Parallels Desktop</a>：在虚拟机上运行 Windows 和 Windows 应用程序</p><h3 id="">开发与生产力</h3><p><a href="https://www.microsoft.com/zh-cn/edge/download?form=MA13FJ">Microsoft Edge</a>：微软 Edge 浏览器，基于 Chromium，扩展（<a href="https://microsoftedge.microsoft.com/addons/detail/wappalyzer-technology-p/mnbndgmknlpdjdnjfmfcdjoegcckoikn">Wappalyzer - 网站技术分析插件</a>、<a href="https://microsoftedge.microsoft.com/addons/detail/itab%E6%96%B0%E6%A0%87%E7%AD%BE%E9%A1%B5/inedkoakiaeepjoblbiiipedngonadhn">iTab新标签页</a>、<a href="https://microsoftedge.microsoft.com/addons/detail/%E6%B2%89%E6%B5%B8%E5%BC%8F%E7%BF%BB%E8%AF%91-ai-%E5%8F%8C%E8%AF%AD%E7%BD%91%E9%A1%B5%E7%BF%BB%E8%AF%91-pdf%E7%BF%BB%E8%AF%91/amkbmndfnliijdhojkpoglbnaaahippg">沉浸式翻译</a>）</p><p><a href="https://www.warp.dev/">Warp</a>：AI 终端，开箱即用，再见 iTerm2  (シ. .)シ</p><p><a href="https://code.visualstudio.com/">Visual Studio Code</a>：VSCode 神来的</p><p><a href="https://cursor.com/cn">Cursor</a>：Cursor 更是神来的</p><p><a href="https://apps.apple.com/cn/app/xcode/id497799835?mt=12">Xcode</a>：跟苹果生态沾点边的还是上 Xcode 叭</p><h3 id="">知识管理</h3><p><a href="https://www.zotero.org/">Zotero</a>：文献管理工具</p><p><a href="https://pdfexpert.com/">PDF Expert</a>：个人觉得是 mac 上最好用的 pdf 软件，2.5.18 版本的就很好了，当时是免费</p><p><a href="https://typora.io/">Typora</a>：Markdown 编辑器</p><p><a href="https://www.notion.com/zh-cn">Notion</a>：笔记、任务、维基和数据库的一体式工作空间</p><p><a href="https://obsidian.md/">Obsidian</a>：笔记记录，构建个人知识体系</p><p>办公软件：<a href="https://weixin.qq.com/">微信</a>、<a href="https://im.qq.com/index/#/">QQ</a>、<a href="https://www.feishu.cn/download">飞书</a>、<a href="https://www.dingtalk.com/download">钉钉</a>、<a href="https://meeting.tencent.com/download/">腾讯会议</a></p><h2 id="">第三乐章：配置艺术</h2><h3 id="">系统设置</h3><p>菜单栏 -&gt; 自动隐藏和显示菜单栏 -&gt; 永不</p><p>键盘 -&gt; 键盘快捷键 -&gt; 修饰键 -&gt; 大写锁定键 映射到 Escape 键（Vim 党又舒服了）</p><p>触控板 -&gt; 开启&quot;轻点以点按&quot;，查询与数据检测器使用&quot;三指轻点&quot;</p><h3 id="dotfiles">Dotfiles</h3><p><a href="https://github.com/Sakulyn/Dotfiles">https://github.com/Sakulyn/Dotfiles</a></p><p>oh-my-zsh、tmux、git、vim 这些简单配下，可以放在一个文件夹里，symbolic link 到根目录下。</p></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tinker/mac-setup#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tinker/mac-setup</link><guid isPermaLink="true">https://sakulyn.top/posts/tinker/mac-setup</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Sun, 14 Jun 2026 15:37:04 GMT</pubDate></item><item><title><![CDATA[部署指北：Mac 本地玩转 Qwen 与 Wan]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tinker/deploy-qwen-and-wan">https://sakulyn.top/posts/tinker/deploy-qwen-and-wan</a></blockquote><div><p>撰写中 qwq</p><h2 id="--">壹 · 前言</h2><p>在<a href="https://huggingface.co/spaces/Vchitect/VBench_Leaderboard">评测集VBench</a>中，Wan2.1 的 Total score 达到了 86.22%</p><p><a href="https://github.com/Wan-Video/Wan2.1">https://github.com/Wan-Video/Wan2.1</a></p>
<h2 id="--">贰 · 原料准备</h2><h2 id="--ollama---qwen">叁 · Ollama -&gt; Qwen</h2><h2 id="--comfyui---wan">肆 · ComfyUI -&gt; Wan</h2><p>ComfyUI  是一款基于节点式图形交互界面的 AI 视频/图像生成工具。由于其极高的自由度和卓越的显存优化，目前已成为运行 Wan2.2 等前沿大模型的主流平台。主要有三种<a href="https://github.com/Comfy-Org/ComfyUI#installing">安装方式</a>：comfy 官网下载 <a href="https://comfy.org/zh-CN/download">ComfyUI 桌面版</a>、comfy-cli 命令行极速安装、手动源码安装。这里我采用 comfy-cli 的方式。</p><h3 id="step-1-conda-">Step 1: conda 环境准备</h3><p>不建议直接在 conda 的 base 环境下安装，我们新起一个环境专门装 comfy。</p><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">conda create -n comfy python=3.12
conda activate comfy
</code></pre>
<h3 id="step-2comfy-">Step 2：Comfy 安装</h3><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">pip install comfy-cli
comfy install
</code></pre>
<h3 id="step-3comfyui-">Step 3：ComfyUI 启动</h3><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">comfy launch
</code></pre>
<p>启动后看到如下日志输出：</p><pre class="language-text lang-text"><code class="language-text lang-text">Launching ComfyUI from: /Users/xxx/Documents/comfy/ComfyUI
...
[INFO] Total VRAM 65536 MB, total RAM 65536 MB
[INFO] pytorch version: 2.14.0.dev20260612
[INFO] Mac Version (26, 5)
[INFO] Set vram state to: SHARED
[INFO] Device: mps
[INFO] [START] ComfyUI-Manager
[ComfyUI-Manager] Using GitPython backend
...
[INFO] Starting server

[INFO] To see the GUI go to: http://127.0.0.1:8188
</code></pre>
<p>日志说明 Comfy 已启用 MPS 加速，浏览器打开 <code>http://127.0.0.1:8188</code> 即可看到界面。</p><h3 id="step-4workflow">Step 4：Workflow</h3><p><a href="https://docs.comfy.org/tutorials/video/wan/wan2_2">https://docs.comfy.org/tutorials/video/wan/wan2_2</a></p><p>Wan2.1模型支持文生视频和图生视频两种方式，每种方式下又有14B和1.3B两种尺寸的模型，其中：</p><p>文生视频</p><ul><li>wan2.1_t2v_1.3B的模型，最大只支持生成832×480像素视频</li><li>wan2.1_t2v_14B的模型，支持1280×720像素和832×480像素视频</li></ul><p>图生视频</p><ul><li>wan2.1_i2v_480p_14B的模型，最大支持生成832×480像素视频</li><li>wan2.1_i2v_720p_14B的模型，最大支持生成1280×720像素视频</li></ul><pre class=""><code class="">ComfyUI/
├───📂 models/
│   ├───📂 diffusion_models/
│   │   └───wan2.2_ti2v_5B_fp16.safetensors
│   ├───📂 text_encoders/
│   │   └─── umt5_xxl_fp8_e4m3fn_scaled.safetensors 
│   └───📂 vae/
│       └── wan2.2_vae.safetensors
</code></pre></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tinker/deploy-qwen-and-wan#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tinker/deploy-qwen-and-wan</link><guid isPermaLink="true">https://sakulyn.top/posts/tinker/deploy-qwen-and-wan</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Sun, 14 Jun 2026 03:34:12 GMT</pubDate></item><item><title><![CDATA[Mac 下 VS Code 搭建优雅的 LaTeX 写作环境]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tinker/vscode-latex-setup">https://sakulyn.top/posts/tinker/vscode-latex-setup</a></blockquote><div><p>在 Mac 上使用 LaTeX 写学术论文时，完整的 MacTeX 安装包通常体积庞大，且包含了许多我们用不到的 GUI 工具（如 TeXShop）。既然我们已经有了强大的 VS Code，完全可以选择安装精简版的命令行 TeX 环境，配合 VS Code 插件打造一个高效、轻量的排版工作流。</p><p>本文将记录如何在 <strong>Mac 平台</strong>上从零配置 <strong>VS Code + LaTeX</strong> 环境，并解决<strong>代码格式化（latexindent）</strong>的依赖问题。</p><h2 id="--">壹 · 安装编译环境</h2><p>推荐使用 Homebrew 一键安装不带 GUI 的精简版 MacTeX。
打开终端，运行以下命令：</p><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">brew install --cask mactex-no-gui
</code></pre>
<p>安装完成后，验证是否安装成功并检查版本：</p><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">tex --version
</code></pre>
<p>如果输出类似如下的版本信息，说明底层编译环境已经就绪。</p><pre class="language-txt lang-txt"><code class="language-txt lang-txt">TeX 3.141592653 (TeX Live 2026)
kpathsea version 6.4.2
Copyright 2026 D.E. Knuth.
There is NO warranty.  Redistribution of this software is
covered by the terms of both the TeX copyright and
the Lesser GNU General Public License.
For more information about these matters, see the file
named COPYING and the TeX source.
Primary author of TeX: D.E. Knuth.
</code></pre>
<h2 id="--vs-code-">贰 · VS Code 插件与核心配置</h2><p>在 VS Code 的扩展商店中搜索并安装 LaTeX Workshop 插件。</p><p>安装完成后，打开 VS Code 的 settings.json（可通过 Cmd + Shift + P 搜索 &quot;Open Settings (JSON)&quot;），将以下配置添加进去：</p><pre class="language-json lang-json"><code class="language-json lang-json">{
  &quot;files.autoSave&quot;: &quot;afterDelay&quot;,
  &quot;editor.formatOnSave&quot;: true,
  &quot;editor.wordWrap&quot;: &quot;on&quot;,
  // ================= LaTeX Workshop 配置 =================
  // 开启文件更改时自动编译
  &quot;latex-workshop.latex.autoBuild.run&quot;: &quot;onFileChange&quot;,
  &quot;latex-workshop.showContextMenu&quot;: true,
  &quot;latex-workshop.intellisense.package.enabled&quot;: true,
  // 屏蔽错误和警告弹窗，改为在控制台查看
  &quot;latex-workshop.message.error.show&quot;: false,
  &quot;latex-workshop.message.warning.show&quot;: false,
  // 指定格式化工具
  &quot;latex-workshop.formatting.latex&quot;: &quot;latexindent&quot;,
  // 定义编译工具
  &quot;latex-workshop.latex.tools&quot;: [
    {
      &quot;name&quot;: &quot;xelatex&quot;,
      &quot;command&quot;: &quot;xelatex&quot;,
      &quot;args&quot;: [
        &quot;-synctex=1&quot;,
        &quot;-interaction=nonstopmode&quot;,
        &quot;-file-line-error&quot;,
        &quot;%DOCFILE%&quot;
      ]
    },
    {
      &quot;name&quot;: &quot;bibtex&quot;,
      &quot;command&quot;: &quot;bibtex&quot;,
      &quot;args&quot;: [&quot;%DOCFILE%&quot;]
    }
  ],
  // 定义编译配方 (Recipes)
  &quot;latex-workshop.latex.recipes&quot;: [
    {
      &quot;name&quot;: &quot;XeLaTeX&quot;,
      &quot;tools&quot;: [&quot;xelatex&quot;]
    },
    {
      &quot;name&quot;: &quot;BibTeX&quot;,
      &quot;tools&quot;: [&quot;bibtex&quot;]
    },
    {
      &quot;name&quot;: &quot;xelatex -&gt; bibtex -&gt; xelatex*2&quot;,
      &quot;tools&quot;: [&quot;xelatex&quot;, &quot;bibtex&quot;, &quot;xelatex&quot;, &quot;xelatex&quot;]
    }
  ],
  // 配置需要自动清理的临时文件类型
  &quot;latex-workshop.latex.clean.fileTypes&quot;: [
    &quot;*.aux&quot;, &quot;*.bbl&quot;, &quot;*.blg&quot;, &quot;*.idx&quot;, &quot;*.ind&quot;, 
    &quot;*.lof&quot;, &quot;*.lot&quot;, &quot;*.out&quot;, &quot;*.toc&quot;, &quot;*.acn&quot;, 
    &quot;*.acr&quot;, &quot;*.alg&quot;, &quot;*.glg&quot;, &quot;*.glo&quot;, &quot;*.gls&quot;, 
    &quot;*.ist&quot;, &quot;*.fls&quot;, &quot;*.log&quot;, &quot;*.fdb_latexmk&quot;
  ],
  // 编译失败时自动清理中间文件
  &quot;latex-workshop.latex.autoClean.run&quot;: &quot;onFailed&quot;,
  // 默认使用上次的配方
  &quot;latex-workshop.latex.recipe.default&quot;: &quot;lastUsed&quot;,
  // 开启 PDF 正反向同步（双击 PDF 对应跳转至源码）
  &quot;latex-workshop.view.pdf.internal.synctex.keybinding&quot;: &quot;double-click&quot;
}
</code></pre>
<p>配置解析：</p><ul><li>默认使用 XeLaTeX：对中文支持更好。</li><li>自动化清理：LaTeX 编译会产生大量临时文件（如 .aux, .log 等），上述配置会在编译失败时自动清理这些无用文件，保持目录整洁。</li><li>SyncTeX 双向跳转：在内置 PDF 阅读器中双击内容，即可快速跳转到 .tex 源码的对应行，极大提升修改效率。</li></ul><h2 id="--tex-">叁 · TEX 代码格式化</h2><p>我们在配置中开启了 <code>editor.formatOnSave</code>，并指定了 <code>latexindent</code> 作为格式化工具。虽然 MacTeX 包含了 <code>latexindent</code>，但它是由 Perl 编写的，macOS 默认的 Perl 环境缺少一些必要的依赖包，直接保存时会导致格式化失败。</p><p>首先，确认 latexindent 是否已在环境变量中：</p><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">which latexindent
</code></pre>
<p>接着，我们需要安装 Perl 的包管理器 cpanminus 以及相关的依赖模块。在终端依次执行以下命令（需要输入系统密码以提权）：</p><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh"># 安装 cpanminus
sudo cpan App::cpanminus

# 使用 cpanm 安装 latexindent 所需的核心依赖
sudo cpanm File::HomeDir YAML::Tiny Log::Log4perl Log::Dispatch Unicode::GCString
</code></pre>
<p>等待依赖全部安装完成即可。</p><h2 id="--">肆 · 测试运行</h2><p>至此，所有的配置工作就结束了。我们可以新建一个 test.tex 文件来进行最终测试。
你可以将以下最简测试代码复制进去：</p><pre class="language-tex lang-tex"><code class="language-tex lang-tex">\documentclass{article}
\begin{document}
    Hello, LaTeX in VS Code!
\end{document}
</code></pre>
<p>测试步骤：</p><ul><li><p>测试格式化依赖：在终端中输入 <code>latexindent test.tex</code>。如果终端正常输出了格式化后的代码，且没有报任何 Perl 相关的错误，说明你的依赖安装没问题。</p></li><li><p>测试自动编译：打乱代码缩进，然后按下 Cmd + S 保存。能够看到代码被自动格式化整齐，并且 VS Code 底部状态栏会出现编译进度。</p></li><li><p>预览 PDF：编译完成后，点击 VS Code 左侧边栏的 LaTeX 图标（或者使用快捷键 Cmd + Option + V），选择 View LaTeX PDF，即可在右侧分栏看到生成的 PDF 预览。</p></li></ul></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tinker/vscode-latex-setup#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tinker/vscode-latex-setup</link><guid isPermaLink="true">https://sakulyn.top/posts/tinker/vscode-latex-setup</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Sun, 17 May 2026 13:51:27 GMT</pubDate></item><item><title><![CDATA[Claude Code Agent 设计机制]]></title><description><![CDATA[<link rel="preload" as="image" href="https://image-proxy.lin-85e.workers.dev/sakulyn/picture-bed/main/cc-architecture.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tech/cc-agent-design">https://sakulyn.top/posts/tech/cc-agent-design</a></blockquote><div><p>八二定理：80% 的能力取决于模型，剩余 20% 来自 cli。</p><p>QA 模式：</p><pre class="language-text lang-text"><code class="language-text lang-text">user message + &lt;attachment&gt; -&gt; model -&gt; answer message
</code></pre>
<p>TASK 模式:</p><pre class="language-text lang-text"><code class="language-text lang-text">user message + &lt;attachment&gt; -&gt; model -&gt; temp message + tool_use -&gt; exec tool -&gt; model -&gt; response
</code></pre>
<div></div><blockquote class="markdown-alert-note"><header>NOTE</header><p>agent 和编排式workflow区别：</p><p>workflow式的设计通常有while循环、if-else之类，没有达到某个条件就一直不退出；而 agent 是在无形中隐式循环，只要是模型对你有一个工具执行的输出，那么 cli 就一定会帮这个模型把对应的工具做一个执行，哪怕是一个错误的结果，也会把这个信息返回给它，而这个循环的开始和结束是模型在控制的，cli 是一个执行者。</p></blockquote>
<p>一个 cli 的简单架构模块：</p><ul><li>context（整个系统固定写好的一些信息）</li><li>tool use（工具执行中心）</li><li>message + llm（消息处理，和模型通信）</li></ul><p>这三个核心模块足以构成cli一个最小化的结构，也就是需要给出相关系统上下文，一个脚手架执行的能力，还有一个互动通信的地方。</p><p>cc 在这最小化的三个核心元素的 cli 上还引入了非常多的东西，比如在工具执行的前和后加上了hook（preToolUse、afterToolUse），context 层面这有memory、system prompt（可以做一些 diy：action style）、env。</p><img src="https://image-proxy.lin-85e.workers.dev/sakulyn/picture-bed/main/cc-architecture.png" width="100%"/><p>简单说一下cc这个架构，</p><ul><li>用户界面层就是 UI，给用户一个大输入框，显示消息的一个组件（用户的消息以及来自模型、工具执行结果的消息），还有另外一个单独的模态框（比如当前要执行一个bash工具，弹出来问你是否允许）。</li><li>组件协调层就是维护上面这些 UI 的生命周期，不管是输入框还是模态框，这些东西都需要状态管理机制。</li><li>核心来到 query，每次发消息触发一个query，这个query执行之后，如果有新的工具执行，它就会在递归的地方query（相当于这个执行结果作为一个新的user message再次发给这个模型从而触发query自身的再次调用）。</li><li>消息管理一方面是由于你是流式的拿到整个消息，你下一轮请求模型之前要把这些消息放到一个正确的顺序上（比如你并发执行了多个工具，需要通过这些消息ID的关联进行一个拼接），另一方面就是涉及模型传输协议的一些小问题。</li><li>AI 交互就比如 querySonnet、queryHaiku 这些，发起请求，做流式处理。</li><li>权限管理就是看一个命令是否危险，还有就是看用户是否放行了这个工具的执行权限，如果用户没有放行的话，就弹出对话框让用户确认是否要执行这个命令。</li><li>工具执行层涉及到外部的一些MCP工具以及内部的bash、write、edit等等这些工具，他们统一注册到一个工具中心上，模型不用管他们是内部还是外部的，对模型来说就是收到一堆允许调用的工具集合以及每一个工具的参数描述和作用描述。</li><li>并发控制就是，我们可能收到一个列表要并行调用一堆的工具使用，这时候就可以看哪些工具能够并发去执行，这样可以加快整个系统运行。</li><li>数据存储层就是做一个持久化保存，比如你不小心把终端关了或者啥的，你下次打开这个项目文件夹要把之前agent所有对话的上下文信息都恢复出来，这就依赖数据的一个持久化。配置文件就是你在启动cli的时候去加载的一些信息。另外就是模型请求过程中或者工具执行发生了一些错误，就可以把这些error信息统一打到一个log文件里。</li></ul><h2 id="">参考文献</h2><ul><li><a href="https://learn.shareai.run/zh/">Learn Claude Code</a></li></ul></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tech/cc-agent-design#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tech/cc-agent-design</link><guid isPermaLink="true">https://sakulyn.top/posts/tech/cc-agent-design</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Mon, 04 May 2026 11:34:31 GMT</pubDate></item><item><title><![CDATA[Tailscale 异地组网！随时随地访问你的设备]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tinker/tailscale-rdp">https://sakulyn.top/posts/tinker/tailscale-rdp</a></blockquote><div><p>玩 Tailscale 起因是想折腾一下远程，主包是个多设备党，把我这些电脑、手机、服务器放到一个虚拟局域网里，这样我就能——</p><ul><li>实现“开发环境”的无缝漫游</li><li>在 Mac 上远程打 Windows 游戏</li><li>随时随地访问设备文件资源</li></ul><h2 id="--">壹 · 不同场景下的远程方案</h2><table><thead><tr><th style="text-align:left"> 访问端 (Client) </th><th style="text-align:left"> 被控端 (Host) </th><th style="text-align:left"> 推荐方案 / 工具 </th><th style="text-align:left"> 核心优势 </th></tr></thead><tbody><tr><td style="text-align:left"> <strong>🖥️Windows</strong> </td><td style="text-align:left"> <strong>🖥️Windows</strong> </td><td style="text-align:left"> <strong>mstsc</strong> (专业版系统自带，家庭版打<a href="https://github.com/stascorp/rdpwrap">RDPWrap</a>补丁) </td><td style="text-align:left"> 操作丝滑，无需安装 </td></tr><tr><td style="text-align:left"> <strong>🍎Mac / iPad</strong> </td><td style="text-align:left"> <strong>🖥️Windows</strong> </td><td style="text-align:left"> <strong><a href="https://apps.apple.com/us/app/microsoft-remote-desktop/id1295203466">Microsoft Remote Desktop</a></strong> </td><td style="text-align:left"> 微软官方出品 </td></tr><tr><td style="text-align:left"> <strong>🖥️Windows</strong> </td><td style="text-align:left"> <strong>🍎Mac</strong> </td><td style="text-align:left"> <strong><a href="https://devolutions.net/remote-desktop-manager/">Remote Desktop Manager</a></strong> </td><td style="text-align:left"> 选择 <strong>ARD (Apple Remote Desktop)</strong> 协议，远比 VNC 流畅 </td></tr><tr><td style="text-align:left"> <strong>🍎Mac</strong> </td><td style="text-align:left"> <strong>🍎Mac</strong> </td><td style="text-align:left"> <strong>屏幕共享</strong> (系统自带) </td><td style="text-align:left"> 苹果原生全家桶加持，延迟极低 </td></tr><tr><td style="text-align:left"> <strong>🖥️任意设备</strong> </td><td style="text-align:left"> <strong>🐧Linux</strong> </td><td style="text-align:left"> 直接<strong>SSH</strong>得了 </td><td style="text-align:left"> 终端操作，稳定且高效 </td></tr></tbody></table><p>都说远程了，为啥不用 ToDesk 或者向日葵呢？</p><ul><li>一方面，使用 ToDesk，你的设备连接、甚至数据流转都需要经过其商业公司的中心服务器，完全黑盒。</li><li>另一方面，它的原理更接近“录屏传输”。软件抓取你的桌面图像，编码压缩成视频流发给另一端，再解码显示。这会导致画质损失、色彩偏色，且在高分辨率下非常吃 CPU，而且 ToDesk 等商业软件的免费版通常会限制带宽、限制帧率、限制多屏幕显示。</li></ul><p>所以说还是原生协议 RDP（基于 TCP/UDP 直连）好，我们只需要解决不在同一个局域网下怎么连接的问题了。</p><h2 id="---tailscale">贰 · 为什么是 Tailscale？</h2><p>Tailscale 基于 WireGuard 协议，能让你在复杂的网络环境下（比如大内网、无公网 IP）实现设备间的点对点直连。简单来说 Tailscale 解决的就是“连接”的问题，它构建了一个虚拟局域网，让你的所有设备无论身处何处，都像在同一个路由器下，这样就随我 SSH 或者 RDP了。</p><p>另一个比较关键的考虑是安全性，Tailscale 建立的是端到端的加密通道，流量不经过中心服务器解密，即便在最坏的情况下（打洞失败，走中继服务器 DERP），数据流也是加密的，中间节点无法窥探内容。</p><h2 id="----derp-">叁 · 从“能用”变成“好用”—— 自建 DERP 中继</h2><p>虽然 Tailscale 的 P2P 打洞能力极强，但在一些复杂的校园网或办公网环境下，偶尔会失败。这时流量会走官方的中继服务器，由于服务器在海外，延迟可能会飙升。</p><p>要达到一个丝滑的体验，解决办法就是利用一台国内云服务器自建 DERP 节点，能将延迟从几百毫秒压低到 50ms 以内，可以参考<a href="https://www.bilibili.com/video/BV1Wh411A73b">Tailscale玩法之内网穿透、异地组网、全隧道模式、纯IP的双栈DERP搭建、Headscale协调服务器搭建，用一期搞定，看一看不亏吧？</a>自行搭建。</p><p><a href="https://www.bilibili.com/video/BV1Wh411A73b">https://www.bilibili.com/video/BV1Wh411A73b</a></p><p>&lt;------空了整理一下搭建过程中遇到的几个小问题------&gt;</p><p>加入网络后，每台设备都会获得一个 100.x.x.x 的固定 IP。配合 MagicDNS，你可以直接通过设备名访问。只要设备在网里，所有端口默认全通，直接告别frp内网穿透的繁琐。比如我在电脑上启 Vite 开发环境，我要在移动端（手机/iPad）上调试，直接输入对应 url 就好，像访问本地 localhost 一样实时预览。</p><h2 id="--">肆 · 踩坑日记</h2><ul><li>Windows 被控端除了开启远程桌面，还要在防火墙里放行 TCP 3389 端口。</li><li>别让电脑睡着了💤，可以设置锁定但不休眠。一旦休眠，Tailscale 也会断开，你就只能回实验室手动开机了。</li></ul></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tinker/tailscale-rdp#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tinker/tailscale-rdp</link><guid isPermaLink="true">https://sakulyn.top/posts/tinker/tailscale-rdp</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Mon, 13 Apr 2026 06:19:05 GMT</pubDate></item><item><title><![CDATA[算法博物馆]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tech/algo-museum">https://sakulyn.top/posts/tech/algo-museum</a></blockquote><div><p>搭配 <a href="https://leetcode.cn/discuss/post/3141566/ru-he-ke-xue-shua-ti-by-endlesscheng-q3yd/">0x3f 的科学刷题</a>食用更佳 : ）</p><h1 id="">滑动窗口</h1><h2 id="">定长滑动窗口</h2><p>滑动窗口长度为定值<code>k</code>，考虑窗口向后滑动时即将离开的元素，做相应更新。</p><p>以长度为 K 子数组中的最大和为例，代码如下。</p><pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var foobar = function(nums, k) {
    let ans = 0, sum = 0;
    for(let i = 0; i &lt; nums.length; ++i) {
        sum += nums[i];
        let left = i - k + 1;
        if(left &lt; 0)
            continue;
        ans = Math.max(ans, sum);
        sum -= nums[left]; // nums[left] 离开窗口，更新
    }
    return ans;
};
</code></pre>
<h2 id="">不定长滑动窗口</h2><p>不定长滑动窗口主要分为三类：求最长子数组，求最短子数组，求子数组个数。</p><p>注：滑动窗口相当于在维护一个队列。右指针的移动可以视作入队，左指针的移动可以视作出队。</p><h3 id="">越短越合法/求最长/最大</h3><p>以无重复字符的最长子串为例，代码如下。</p><pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {number}
 */
var foobar = function (s) {
    const map = new Map();
    let ans = 0;
    for (let i = 0, l = 0; i &lt; s.length; ++i) {
        map.set(s[i], (map.get(s[i]) ?? 0) + 1);
        while (map.get(s[i]) &gt; 1) {
            map.set(s[l], (map.get(s[l]) ?? 0) - 1);
            l++;
        }
        ans = Math.max(ans, i - l + 1);
    }
    return ans;
};
</code></pre>
<h3 id="">越长越合法/求最短/最小</h3><p>长度最小的子数组为例，代码如下。</p><pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function (target, nums) {
    let n = nums.length, ans = n + 1;
    for (let i = 0, l = 0, sum = 0; i &lt; n; ++i) {
        sum += nums[i];
        while (sum &gt;= target) {
            ans = Math.min(ans, i - l + 1);
            sum -= nums[l];
            l++;
        }
    }
    return ans &lt; n + 1 ? ans : 0;
};
</code></pre>
<h3 id="">求子数组个数</h3><h4 id="">越短越合法</h4><h2 id="">二分查找</h2><p>使用二分查找算法的前提一般是数组是排好序的</p><pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function (nums, target) {
    let left = -1, right = nums.length;
    while (left + 1 &lt; right) {
        let mid = Math.floor((left + right) / 2);
        if (nums[mid] &lt; target)
            left = mid;
        else
            right = mid;
    }
    return right;
};
</code></pre></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tech/algo-museum#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tech/algo-museum</link><guid isPermaLink="true">https://sakulyn.top/posts/tech/algo-museum</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Thu, 12 Mar 2026 16:44:13 GMT</pubDate></item><item><title><![CDATA[Leetcode Hot 100]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/archive/lc-hot100">https://sakulyn.top/posts/archive/lc-hot100</a></blockquote><div><blockquote class="markdown-alert-tip"><header>TIP</header><p>力扣 JS 内置 lodash 和 队列 (datastructures-js/queue) / 优先队列 (datastructures-js/priority-queue) 库</p></blockquote>
<p>梦开始的地方——</p><h2 id="httpsleetcodecnproblemstwo-sumdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/two-sum/description/?envType=study-plan-v2&amp;envId=top-100-liked">两数之和</a></h2><p>核心思路就是哈希表一次遍历。在遍历数组 nums 的同时，利用哈希表记录已经访问过的数值及其下标。对于当前的元素 <code>nums[i]</code>，我们不需要二次遍历另一个数判断两数和是否为 <code>target</code>，而是寻找“目标差值”<code>target - nums[i]</code>。若目标差值已在哈希表中，说明找到了匹配对，直接返回其下标。若不在，则将当前 <code>nums[i]</code> 存入哈希表，继续向后扫描。</p><p>时间复杂度：O(n)</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; twoSum(vector&lt;int&gt;&amp; nums, int target) {
        unordered_map&lt;int, int&gt; mp;
        for (int i = 0; i &lt; nums.size(); ++i) {
            int x = target - nums[i];
            if (mp.count(x))
                return {mp[x], i};
            mp[nums[i]] = i;
        }
        return {};
    }
};
</code></pre><pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (nums, target) {
    const map = new Map();
    for (let i = 0; i &lt; nums.length; ++i) {
        if (map.get(target - nums[i]) !== undefined)
            return [i, map.get(target - nums[i])];
        map.set(nums[i], i);
    }
    return [];
};
</code></pre>
<h2 id="httpsleetcodecnproblemsgroup-anagramsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/group-anagrams/description/?envType=study-plan-v2&amp;envId=top-100-liked">字母异位词分组</a></h2><p>哈希表分组，将<code>strs[i]</code>排序后的值作为哈希表的键，其在结果数组里的下标作为哈希表的值。也可以不用索引映射，直接用<code>vector&lt;string&gt;</code>作为哈希表的值。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;string&gt;&gt; groupAnagrams(vector&lt;string&gt;&amp; strs) {
        vector&lt;vector&lt;string&gt;&gt; ans;
        unordered_map&lt;string, int&gt; mp;
        for (int i = 0, j = 0; i &lt; strs.size(); ++i) {
            string t = strs[i];
            ranges::sort(t);
            if (!mp.count(t)) {
                mp[t] = j++;
                ans.push_back({strs[i]});
            } else
                ans[mp[t]].push_back(strs[i]);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string[]} strs
 * @return {string[][]}
 */
var groupAnagrams = function (strs) {
    let ans = [];
    const map = new Map();
    for (let i = 0, j = 0; i &lt; strs.length; ++i) {
        let t = [...strs[i]].sort().join();
        if (map.get(t) === undefined) {
            map.set(t, j++);
            ans.push([strs[i]]);
        } else {
            ans[map.get(t)].push(strs[i]);
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemslongest-consecutive-sequencedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/longest-consecutive-sequence/description/?envType=study-plan-v2&amp;envId=top-100-liked">最长连续序列</a></h2><p>要找出数组中数字连续的最长序列，那么从这个序列的起点开始找就行了，不需要从数组中每个数开始找一遍。我们怎么找到序列的起点？哈希表中如果不存在当前值<code>x</code>小<code>1</code>的数<code>x-1</code>，那么<code>x</code>就是起点。找到起点后通过哈希表找终点，数后面有多长。</p><p>时间复杂度：O(n)</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int longestConsecutive(vector&lt;int&gt;&amp; nums) {
        unordered_set&lt;int&gt; st(nums.begin(), nums.end());
        int ans = 0;
        for (auto start : st) {
            if (!st.count(start - 1)) {
                int end = start + 1;
                while (st.count(end))
                    end++;
                ans = max(ans, end - start);
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var longestConsecutive = function (nums) {
    let ans = 0;
    const set = new Set(nums);
    for (let start of set) {
        if (!set.has(start - 1)) {
            let end = start + 1;
            while (set.has(end))
                end++;
            ans = Math.max(ans, end - start);
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmove-zeroesdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/move-zeroes/description/?envType=study-plan-v2&amp;envId=top-100-liked">移动零</a></h2><p>双指针，把右边所有非零元素移到左边的空位（<code>0</code>）上。</p><p>时间复杂度：O(n)</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    void moveZeroes(vector&lt;int&gt;&amp; nums) {
        for (int l = 0, r = 0; r &lt; nums.size(); ++r) {
            if (nums[r]) {
                swap(nums[l++], nums[r]);
            } 
        }
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    for(let l = 0, r = 0; r &lt; nums.length; ++r) {
        if(nums[r]) {
            [nums[l], nums[r]] = [nums[r], nums[l]];
            l++;
        }
    }
};
</code></pre>
<h2 id="httpsleetcodecnproblemscontainer-with-most-waterdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/container-with-most-water/description/?envType=study-plan-v2&amp;envId=top-100-liked">盛最多水的容器</a></h2><p>水量即容器的面积，面积=底*高，求最大水量也就是说要让底和高尽可能大，用双指针分别置于数组首尾，逐渐减小底，我们知道高是由左右指针对应的最小值决定的，如果左边的高小于右边的，就移动左指针（因为如果移动较高的一侧，新的高度即使超过原来高度，容器的高取的是最小值，还是左边的高，那么宽也减小的情况下，面积不可能会增大），否则移动右指针。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int maxArea(vector&lt;int&gt;&amp; height) {
        int ans = 0, l = 0, r = height.size() - 1;
        while (l &lt; r) {
            ans = max(ans, (r - l) * min(height[l], height[r]));
            height[l] &lt; height[r] ? l++ : r--;
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function (height) {
    let ans = 0, l = 0, r = height.length - 1;
    while (l &lt; r) {
        ans = Math.max(ans, (r - l) * Math.min(height[l], height[r]));
        if (height[l] &lt; height[r])
            l++;
        else
            r--;
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblems3sumdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/3sum/description/?envType=study-plan-v2&amp;envId=top-100-liked">三数之和</a></h2><p>需要考虑如何避免重复的三元组。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; threeSum(vector&lt;int&gt;&amp; nums) {
        vector&lt;vector&lt;int&gt;&gt; ans;
        ranges::sort(nums);
        int n = nums.size();
        for (int i = 0; i &lt; n - 2; ++i) {
            if (i &amp;&amp; nums[i] == nums[i - 1])
                continue;
            if (nums[i] + nums[i + 1] + nums[i + 2] &gt; 0)
                break;
            if (nums[i] + nums[n - 2] + nums[n - 1] &lt; 0)
                continue;
            int j = i + 1, k = n - 1;
            while (j &lt; k) {
                int sum = nums[i] + nums[j] + nums[k];
                if (sum &lt; 0)
                    j++;
                else if (sum &gt; 0)
                    k--;
                else {
                    if (j == i + 1 || nums[j] != nums[j - 1])
                        ans.push_back({nums[i], nums[j], nums[k]});
                    j++;
                    k--;
                }
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
    nums.sort((a, b) =&gt; a - b);
    let ans = [], n = nums.length;
    for (let i = 0; i &lt; n - 2; ++i) {
        if (i &amp;&amp; nums[i] === nums[i - 1])
            continue;
        if (nums[i] + nums[i + 1] + nums[i + 2] &gt; 0)
            break;
        if (nums[i] + nums[n - 2] + nums[n - 1] &lt; 0)
            continue;
        let j = i + 1, k = n - 1;
        while (j &lt; k) {
            let sum = nums[i] + nums[j] + nums[k];
            if (sum &lt; 0)
                j++;
            else if (sum &gt; 0)
                k--;
            else {
                if (j == i + 1 || nums[j] != nums[j - 1])
                    ans.push([nums[i], nums[j], nums[k]]);
                j++;
                k--;
            }
        }

    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemstrapping-rain-waterdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/trapping-rain-water/description/?envType=study-plan-v2&amp;envId=top-100-liked">接雨水</a></h2><p>双指针的做法就是维护两边最大的高度，哪边低哪边内部就接雨水。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int trap(vector&lt;int&gt;&amp; height) {
        int ans = 0;
        vector&lt;int&gt; v;
        for (int i = 0; i &lt; height.size(); ++i) {
            while (!v.empty() &amp;&amp; height[i] &gt;= height[v.back()]) {
                int j = v.back();
                v.pop_back();
                if (!v.empty()) {
                    int k = v.back();
                    ans += (min(height[k], height[i]) - height[j]) * (i - k - 1);
                }
            }
            v.push_back(i);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function (height) {
    let ans = 0, preMax = 0, sufMax = 0, l = 0, r = height.length - 1;
    while (l &lt; r) {
        preMax = Math.max(preMax, height[l]);
        sufMax = Math.max(sufMax, height[r]);
        if (preMax &lt; sufMax)
            ans += preMax - height[l++];
        else
            ans += sufMax - height[r--];
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemslongest-substring-without-repeating-charactersdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/?envType=study-plan-v2&amp;envId=top-100-liked">无重复字符的最长子串</a></h2><p>大体思路是哈希表/哈希集合+不定长滑动窗口，有重复字符就移动左指针。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int ans = 0;
        unordered_map&lt;char, int&gt; mp;
        for (int i = 0, l = 0; i &lt; s.size(); ++i) {
            mp[s[i]]++;
            while (mp[s[i]] &gt; 1)
                mp[s[l++]]--;
            ans = max(ans, i - l + 1);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function (s) {
    let ans = 0;
    const set = new Set();
    for (let i = 0, l = 0; i &lt; s.length; ++i) {
        while (set.has(s[i]))
            set.delete(s[l++]);
        set.add(s[i]);
        ans = Math.max(ans, i - l + 1);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsfind-all-anagrams-in-a-stringenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/find-all-anagrams-in-a-string/?envType=study-plan-v2&amp;envId=top-100-liked">找到字符串中所有字母异位词</a></h2><p>定长滑动窗口，枚举<code>s</code>中所有与<code>p</code>等长的字串。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; findAnagrams(string s, string p) {
        vector&lt;int&gt; ans;
        unordered_map&lt;char, int&gt; mp_s, mp_p;
        for (auto ch : p)
            mp_p[ch]++;
        int n = p.size();
        for (int i = 0; i &lt; s.size(); ++i) {
            mp_s[s[i]]++;
            int l = i - n + 1;
            if (l &lt; 0)
                continue;
            if (mp_p == mp_s)
                ans.push_back(l);
            mp_s[s[l]]--;
            if (mp_s[s[l]] == 0)
                mp_s.erase(s[l]);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function (s, p) {
    let ans = [];
    const cntP = new Array(26).fill(0), cntS = new Array(26).fill(0);
    for (const c of p)
        cntP[c.charCodeAt() - &#x27;a&#x27;.charCodeAt()]++;
    for (let i = 0; i &lt; s.length; ++i) {
        cntS[s[i].charCodeAt() - &#x27;a&#x27;.charCodeAt()]++;
        let l = i - p.length + 1;
        if (l &lt; 0) continue;
        if (_.isEqual(cntS, cntP)) ans.push(l);
        cntS[s[l].charCodeAt() - &#x27;a&#x27;.charCodeAt()]--;
    }
    return ans;
};
</code></pre>
<h2 id="-k-httpsleetcodecnproblemssubarray-sum-equals-kdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/subarray-sum-equals-k/description/?envType=study-plan-v2&amp;envId=top-100-liked">和为 K 的子数组</a></h2><p>哈希表记录前缀和。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int subarraySum(vector&lt;int&gt;&amp; nums, int k) {
        int ans = 0, sum = 0;
        unordered_map&lt;int, int&gt; mp;
        mp[0] = 1;
        for (int i = 0; i &lt; nums.size(); ++i) {
            sum += nums[i];
            ans += mp[sum - k];
            mp[sum]++;
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var subarraySum = function (nums, k) {
    let ans = 0, sum = 0;
    const map = new Map();
    for (const num of nums) {
        map.set(sum, (map.get(sum) ?? 0) + 1);
        sum += num;
        ans += map.get(sum - k) ?? 0;
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemssliding-window-maximumdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/sliding-window-maximum/description/?envType=study-plan-v2&amp;envId=top-100-liked">滑动窗口最大值</a></h2><p>一种思路是用最大堆，堆顶是最大元素及其对应下标，需要滑出左边移出窗口的元素。</p><p>但这不是最好的做法，我们可以用单调队列，需要维护的是窗口内最大元素的下标，每次入队尾前，如果当前元素大于或等于队尾对应元素，就移出队尾这个元素，这样就保证了队列是单调递减的，也就是说队首是最大元素下标，是我们要记录的答案。那队首什么时候移出呢，队首下标如果与当前下标的距离不小于<code>k</code>了，说明队首对应元素离开窗口了，需要移出。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; maxSlidingWindow(vector&lt;int&gt;&amp; nums, int k) {
        vector&lt;int&gt; ans;
        priority_queue&lt;pair&lt;int, int&gt;&gt; heap;
        for (int i = 0; i &lt; nums.size(); ++i) {
            heap.push({nums[i], i});
            while (heap.top().second &lt;= i - k)
                heap.pop();
            if (i &gt;= k - 1)
                ans.push_back(heap.top().first);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function (nums, k) {
    let ans = [];
    const q = new Deque();
    for (let i = 0; i &lt; nums.length; ++i) {
        while (!q.isEmpty() &amp;&amp; nums[i] &gt;= nums[q.back()])
            q.popBack();
        q.pushBack(i);
        let l = i - k + 1;
        if (q.front() &lt; l)
            q.popFront();
        if (l &gt;= 0)
            ans.push(nums[q.front()]);
    }
    return ans;
    // let ans = [], dq = [];
    // for (let i = 0; i &lt; nums.length; ++i) {
    //     while (dq.length &amp;&amp; nums[i] &gt;= nums[dq.at(-1)])
    //         dq.pop();
    //     dq.push(i);
    //     if (i - dq[0] &gt;= k)
    //         dq.shift(); // shift() 也能移除首位元素，但时间复杂度是 O(n)，最好用 Deque 数据结构
    //     if (i &gt;= k - 1)
    //         ans.push(nums[dq[0]]);
    // }
    // return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsminimum-window-substringdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/minimum-window-substring/description/?envType=study-plan-v2&amp;envId=top-100-liked">最小覆盖子串</a></h2><p>考虑滑动窗口，什么时候需要滑动呢？当窗口内的子串满足覆盖 <code>t</code> 时，移动左值针，这里要记录指针的位置，直接更新答案会爆内存。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    string minWindow(string s, string t) {
        int cnt_s[128]{}, cnt_t[128]{};
        for(auto ch: t)
            cnt_t[ch]++;
        auto cover = [&amp;]() -&gt; bool {
            for (int i = &#x27;A&#x27;; i &lt;= &#x27;Z&#x27;; ++i) {
                int j = i - &#x27;A&#x27; + &#x27;a&#x27;;
                if (cnt_t[i] &gt; cnt_s[i] || cnt_t[j] &gt; cnt_s[j])
                    return false;
            }
            return true;
        };
        int left = 0, right = -1;
        for (int l = 0, r = 0; r &lt; s.size(); ++r) {
            cnt_s[s[r]]++;
            while (cover()) {
                if (right == -1 || r - l &lt; right - left) {
                    left = l;
                    right = r;
                }
                cnt_s[s[l++]]--;
            }
        }
        return s.substr(left, right - left + 1);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function (s, t) {
    const cnt_s = new Array(128).fill(0), cnt_t = new Array(128).fill(0);
    for (const ch of t)
        cnt_t[ch.charCodeAt(0)]++;
    function cover() {
        for (let i = 65; i &lt;= 90; ++i) {
            let j = i + 32;
            if (cnt_s[i] &lt; cnt_t[i] || cnt_s[j] &lt; cnt_t[j])
                return false;
        }
        return true;
    }
    let left = 0, right = -1;
    for (let l = 0, r = 0; r &lt; s.length; ++r) {
        cnt_s[s[r].charCodeAt(0)]++;
        while (cover()) {
            if (right == -1 || right - left &gt; r - l) {
                left = l;
                right = r;
            }
            cnt_s[s[l].charCodeAt(0)]--;
            l++;
        }
    }

    return s.slice(left, right + 1);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmaximum-subarraysubmissions708284846envtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/maximum-subarray/submissions/708284846/?envType=study-plan-v2&amp;envId=top-100-liked">最大子数组和</a></h2><p>问题是找到最大和的连续子数组，可以用动态规划，那么就要想状态转移方程是什么：</p><p>定义 <code>f[i]</code> 为截至第<code>i</code>个元素的最大子数组和。</p><p>$$ f(i)=\left{
\begin{aligned}
&amp; max(f(i-1), 0) + nums[i], i \geq 1\
&amp; nums[i], i = 0
\end{aligned}
\right.
$$</p>
<pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int maxSubArray(vector&lt;int&gt;&amp; nums) {
        int sum = nums[0], ans = sum;
        for (int i = 1; i &lt; nums.size(); ++i) {
            sum = max(sum, 0) + nums[i];
            ans = max(ans, sum);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function (nums) {
    let sum = nums[0], ans = sum;
    for (let i = 1; i &lt; nums.length; ++i) {
        sum = Math.max(sum, 0) + nums[i];
        ans = Math.max(ans, sum);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmerge-intervalsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/merge-intervals/description/?envType=study-plan-v2&amp;envId=top-100-liked">合并区间</a></h2><p>这题主要就是先排序，保证左端点是从小到大的。然后判断每个左端点在不在前一个元素区间里，更新右端点为两者右端点的最大值。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; merge(vector&lt;vector&lt;int&gt;&gt;&amp; intervals) {
        ranges::sort(intervals);
        vector&lt;vector&lt;int&gt;&gt; ans{intervals[0]};
        for (int i = 1; i &lt; intervals.size(); ++i) {
            if (intervals[i][0] &gt; ans.back()[1])
                ans.push_back(intervals[i]);
            else
                ans.back()[1] = max(ans.back()[1], intervals[i][1]);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function (intervals) {
    intervals.sort((a, b) =&gt; a[0] - b[0]);
    let ans = [intervals[0]];
    for (let i = 1; i &lt; intervals.length; ++i) {
        if (ans.at(-1)[1] &lt; intervals[i][0])
            ans.push(intervals[i]);
        else
            ans.at(-1)[1] = Math.max(ans.at(-1)[1], intervals[i][1]);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsrotate-arraydescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&amp;envId=top-100-liked">轮转数组</a></h2><p>参考<a href="https://leetcode.cn/problems/rotate-array/solutions/2784427/tu-jie-yuan-di-zuo-fa-yi-tu-miao-dong-py-ryfv/?envType=study-plan-v2&amp;envId=top-100-liked">灵神的思路</a>，先翻转整个数组，在翻转前<code>k</code>个和后<code>n-k</code>个。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    void rotate(vector&lt;int&gt;&amp; nums, int k) {
        k %= nums.size();
        ranges::reverse(nums);
        ranges::reverse(nums.begin(), nums.begin() + k);
        ranges::reverse(nums.begin() + k, nums.end());
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function (nums, k) {
    let n = nums.length;
    k %= n;
    function reverse(start, end) {
        while (start &lt; end) {
            [nums[start], nums[end]] = [nums[end], nums[start]];
            start++;
            end--
        }
    }
    reverse(0, n - 1);
    reverse(0, k - 1);
    reverse(k, n - 1);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsproduct-of-array-except-selfenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/product-of-array-except-self/?envType=study-plan-v2&amp;envId=top-100-liked">除了自身以外数组的乘积</a></h2><p>简单来说就是前缀元素的乘积乘以后缀元素的乘积。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; productExceptSelf(vector&lt;int&gt;&amp; nums) {
        int n = nums.size();
        vector&lt;int&gt; ans, prefix{1}, suffix{1};
        for (int i = 0; i &lt; n; ++i) {
            prefix.push_back(prefix.back() * nums[i]);
            suffix.push_back(suffix.back() * nums[n - i - 1]);
        }
        for (int i = 0; i &lt; n; ++i)
            ans.push_back(prefix[i] * suffix[n - i - 1]);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function (nums) {
    let n = nums.length, ans = [], prefix = [1], suffix = [1];
    for (let i = 0; i &lt; n; ++i) {
        prefix.push(prefix.at(-1) * nums[i]);
        suffix.push(suffix.at(-1) * nums[n - i - 1]);
    }
    for (let i = 0; i &lt; n; ++i)
        ans.push(prefix[i] * suffix[n - i - 1]);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsfirst-missing-positivedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/first-missing-positive/description/?envType=study-plan-v2&amp;envId=top-100-liked">缺失的第一个正数</a></h2><p>问题是找出其中没有出现的最小的正整数，设<code>n</code>为数组长度 + 1，那么答案一定在 区间<code>[1, n]</code>中。先遍历数组，排除区间之外的数，可以将这些元素置<code>0</code>，再次遍历，将元素 <code>nums[i]</code> 对应到 <code>nums[nums[i] -1]</code> 上进行标记，加一个 <code>n</code>，说明可以排除这个下标。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int firstMissingPositive(vector&lt;int&gt;&amp; nums) {
        int n = nums.size() + 1;
        for (auto&amp; num : nums) {
            if (num &lt; 0 || num &gt;= n)
                num = 0;
        }
        for (int i = 0; i &lt; n - 1; ++i) {
            if (nums[i] % n)
                nums[nums[i] % n - 1] += n;
        }
        for (int i = 0; i &lt; n - 1; ++i) {
            if (nums[i] &lt; n)
                return i + 1;
        }
        return n;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var firstMissingPositive = function (nums) {
    let n = nums.length + 1;
    for (let i = 0; i &lt; n - 1; ++i) {
        if (nums[i] &lt; 0 || nums[i] &gt;= n)
            nums[i] = 0;
    }
    for (let i = 0; i &lt; n - 1; ++i) {
        if (nums[i] % n) {
            nums[nums[i] % n - 1] += n;
        }
    }
    for (let i = 0; i &lt; n - 1; ++i) {
        if (nums[i] &lt; n)
            return i + 1;
    }
    return n;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsset-matrix-zeroesenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/set-matrix-zeroes/?envType=study-plan-v2&amp;envId=top-100-liked">矩阵置零</a></h2><p>原地算法，遍历每个元素，如果是<code>0</code>，就标记当前行和列。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    void setZeroes(vector&lt;vector&lt;int&gt;&gt;&amp; matrix) {
        int m = matrix.size(), n = matrix[0].size();
        bool first_row_has_zero = ranges::contains(matrix[0], 0);
        bool first_col_has_zero =
            ranges::any_of(matrix, [](auto&amp; row) { return row[0] == 0; });
        for (int i = 1; i &lt; m; ++i) {
            for (int j = 1; j &lt; n; ++j) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = 0;
                    matrix[0][j] = 0;
                }
            }
        }
        for (int i = 1; i &lt; m; ++i) {
            for (int j = 1; j &lt; n; ++j) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0)
                    matrix[i][j] = 0;
            }
        }
        if (first_col_has_zero) {
            for (int i = 0; i &lt; m; ++i)
                matrix[i][0] = 0;
        }
        if (first_row_has_zero) {
            ranges::fill(matrix[0], 0);
        }
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var setZeroes = function (matrix) {
    const m = matrix.length, n = matrix[0].length;
    let first_row_contains_zero = matrix[0].includes(0);
    for (let i = 1; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (matrix[i][j] === 0)
                matrix[i][0] = matrix[0][j] = 0;
        }
    }
    for (let i = 1; i &lt; m; ++i) {
        for (let j = 1; j &lt; n; ++j) {
            if (matrix[i][0] === 0 || matrix[0][j] === 0)
                matrix[i][j] = 0;
        }
    }
    if(matrix[0][0] === 0) {
        for(const row of matrix)
            row[0] = 0;
    }
    if (first_row_contains_zero)
        matrix[0].fill(0)
};
</code></pre>
<h2 id="httpsleetcodecnproblemsspiral-matrixdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/spiral-matrix/description/?envType=study-plan-v2&amp;envId=top-100-liked">螺旋矩阵</a></h2><p>从外到里遍历，只要考虑好方向变化就行。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; spiralOrder(vector&lt;vector&lt;int&gt;&gt;&amp; matrix) {
        if (matrix.size() == 0)
            return {};
        int a = 0, b = (int)matrix[0].size(), c = 0, d = (int)matrix.size();
        vector&lt;int&gt; res;
        while (a &lt; b &amp;&amp; c &lt; d) {
            for (int i = a; i &lt; b; ++i)
                res.push_back(matrix[c][i]);
            for (int i = c + 1; i &lt; d; ++i)
                res.push_back(matrix[i][b - 1]);
            for (int i = b - 2; i &gt;= a &amp;&amp; d &gt; c + 1; --i)
                res.push_back(matrix[d - 1][i]);
            for (int i = d - 2; i &gt; c &amp;&amp; d &gt; c + 1 &amp;&amp; b &gt; a + 1; --i)
                res.push_back(matrix[i][a]);
            a += 1;
            b -= 1;
            c += 1;
            d -= 1;
        }
        return res;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function (matrix) {
    let dir = 0, i = 0, j = 0, t = 0, ans = [];
    const m = matrix.length, n = matrix[0].length, dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]];
    while (ans.length &lt; m * n) {
        ans.push(matrix[i][j]);
        dir %= 4;
        i += dirs[dir][0];
        j += dirs[dir][1];
        if ((dir === 0 &amp;&amp; j &gt;= n - t) || (dir === 1 &amp;&amp; i &gt;= m - t) || (dir === 2 &amp;&amp; j &lt; t) || (dir === 3 &amp;&amp; i &lt; t)) {
            dir++;
            t += dir % 3 === 0;
            i += dirs[dir % 4][0] - dirs[(dir - 1) % 4][0];
            j += dirs[dir % 4][1] - dirs[(dir - 1) % 4][1];
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsrotate-imageenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/rotate-image/?envType=study-plan-v2&amp;envId=top-100-liked">旋转图像</a></h2><p>先按主对角线反转，再进行行翻转。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    void rotate(vector&lt;vector&lt;int&gt;&gt;&amp; matrix) {
        int n = matrix.size();
        for (int i = 0; i &lt; n; ++i)
            for (int j = i; j &lt; n; ++j)
                swap(matrix[i][j], matrix[j][i]);
        for (int i = 0; i &lt; n; ++i)
            for (int j = 0; j &lt; n / 2; ++j)
                swap(matrix[i][j], matrix[i][n - j - 1]);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var rotate = function (matrix) {
    const m = matrix.length, n = matrix[0].length;
    for (let i = 0; i &lt; m; ++i) {
        for (let j = i; j &lt; n; ++j)
            [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
    }
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n / 2; ++j)
            [matrix[i][j], matrix[i][n - j - 1]] = [matrix[i][n - j - 1], matrix[i][j]];
    }
};
</code></pre>
<h2 id="-iihttpsleetcodecnproblemssearch-a-2d-matrix-iienvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/search-a-2d-matrix-ii/?envType=study-plan-v2&amp;envId=top-100-liked">搜索二维矩阵 II</a></h2><p>从右上角开始找，如果大于目标值，往左，如果小于目标值，往下。当然也可以从左下角开始。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool searchMatrix(vector&lt;vector&lt;int&gt;&gt;&amp; matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int i = 0, j = n - 1;
        while (i &lt; m &amp;&amp; j &gt;= 0) {
            if (matrix[i][j] == target)
                return true;
            else if (matrix[i][j] &gt; target)
                --j;
            else
                ++i;
        }
        return false;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var searchMatrix = function (matrix, target) {
    const m = matrix.length, n = matrix[0].length;
    let i = 0, j = n - 1;
    while (i &lt; m &amp;&amp; j &gt;= 0) {
        if (matrix[i][j] == target)
            return true;
        if (matrix[i][j] &gt; target)
            --j;
        else
            ++i;
    }
    return false;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsintersection-of-two-linked-listsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/intersection-of-two-linked-lists/description/?envType=study-plan-v2&amp;envId=top-100-liked">相交链表</a></h2><p>假设两条链表长度分别为<code>m</code>和<code>n</code>，那么从距离表尾<code>min(m, n)</code>处开始找相同节点就行。</p><p>更简便的方法是让两个指针<code>p</code>、<code>q</code>走相同的距离，假设<code>m = x + z, n = y + z</code>，如果链表不相交，最终都会走到空节点。而链表相交的情况下：</p><ul><li>如果<code>x</code>和<code>y</code>相同，<code>p</code>、<code>q</code>一定会相遇；</li><li>如果<code>x</code>和<code>y</code>不相同，只要让它们同时走<code>x + y + z</code>的长度，走到空节点的时候更新指针指向另一条链表的头节点，继续走，它们也会相遇。</li></ul><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        ListNode *p = headA, *q = headB;
        while (p &amp;&amp; q) {
            p = p-&gt;next;
            q = q-&gt;next;
        }
        ListNode *a = headA, *b = headB;
        while (p) {
            p = p-&gt;next;
            a = a-&gt;next;
        }
        while (q) {
            q = q-&gt;next;
            b = b-&gt;next;
        }
        while (a != b) {
            a = a-&gt;next;
            b = b-&gt;next;
        }
        return a;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function (headA, headB) {
    let p = headA, q = headB;
    while(p !== q) {
        p = p ? p.next : headB;
        q = q ? q.next : headA;
    }
    return p;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsreverse-linked-listenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/reverse-linked-list/?envType=study-plan-v2&amp;envId=top-100-liked">反转链表</a></h2><p>用到<code>pre、cur、nxt</code>三个变量，分别指向前一个节点，当前节点，后一个节点。要翻转链表，更改当前节点<code>next</code>指向<code>pre</code>就行了，循环结束后<code>pre</code>表示新得到的链表。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = nullptr, *cur = head, *nxt = head;
        while (nxt) {
            nxt = cur-&gt;next;
            cur-&gt;next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let pre = null, cur = head, nxt = head;
    while(nxt) {
        nxt = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nxt;
    }
    return pre;
};
</code></pre>
<h2 id="httpsleetcodecnproblemspalindrome-linked-listdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/palindrome-linked-list/description/?envType=study-plan-v2&amp;envId=top-100-liked">回文链表</a></h2><p>这题可以利用上一题的翻转，回文嘛，把链表后半段翻转一下，然后分别从表头和链表中间开始遍历判断节点值是不是相等。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode *p = head, *q = head;
        while (q &amp;&amp; q-&gt;next) {
            p = p-&gt;next;
            q = q-&gt;next-&gt;next;
        }
        ListNode *pre = nullptr, *cur = p, *nxt = p;
        while (nxt) {
            nxt = cur-&gt;next;
            cur-&gt;next = pre;
            pre = cur;
            cur = nxt;
        }
       // 我这里没把后半段链表接上去
        q = pre, p = head;
        while (p &amp;&amp; q) {
            if(p-&gt;val != q-&gt;val)
                return false;
            p = p-&gt;next;
            q = q-&gt;next;
        }
        return true;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {boolean}
 */
var isPalindrome = function (head) {
    let p = head, q = head;
    while (q &amp;&amp; q.next) {
        p = p.next;
        q = q.next.next;
    }
    let pre = null, cur = p, nxt = p;
    while (nxt) {
        nxt = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nxt;
    }
    p = head, q = pre;
    while (p &amp;&amp; q) {
        if (p.val != q.val)
            return false;
        p = p.next;
        q = q.next;
    }
    return true;
};
</code></pre>
<h2 id="httpsleetcodecnproblemslinked-list-cycleenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/linked-list-cycle/?envType=study-plan-v2&amp;envId=top-100-liked">环形链表</a></h2><p>核心思路就是快慢指针，链表有环的话快指针和慢指针一定会相遇。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode* head) {
        ListNode *p = head, *q = head;
        while (q &amp;&amp; q-&gt;next) {
            p = p-&gt;next;
            q = q-&gt;next-&gt;next;
            if (p == q)
                return true;
        }
        return false;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
    let p = head, q = head;
    while (q &amp;&amp; q.next) {
        p = p.next;
        q = q.next.next;
        if (p === q)
            return true;
    }
    return false;
};
</code></pre>
<h2 id="-iihttpsleetcodecnproblemslinked-list-cycle-iidescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/linked-list-cycle-ii/description/?envType=study-plan-v2&amp;envId=top-100-liked">环形链表 II</a></h2><p>同样利用快慢指针。假设头节点到环入口距离为<code>l</code>，环长为<code>c</code>，快慢指针相遇后，距离（顺时针）环入口为<code>x</code>，那么慢指针走过的距离可计算为<code>l+x</code>，快指针走过的距离为<code>l+x+c</code>（快指针实际就是比慢指针多走了一个环的距离），而快指针移动速度是慢指针的两倍，因此我们可以得到等式<code>2(l+x) = l+x+c</code>，化简得<code>l = c-x</code>，这说明什么呢？要找到环入口，继续移动慢指针就行了，另外从头移动另一个指针，他们必定会在环入口相遇，因为<code>l = c-x</code>。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* detectCycle(ListNode* head) {
        ListNode *p = head, *q = head;
        while (q &amp;&amp; q-&gt;next) {
            p = p-&gt;next;
            q = q-&gt;next-&gt;next;
            if (p == q) {
                ListNode* h = head;
                while (h != p) {
                    h = h-&gt;next;
                    p = p-&gt;next;
                }
                return p;
            }
        }
        return NULL;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function (head) {
    let p = head, q = head;
    while (q &amp;&amp; q.next) {
        p = p.next;
        q = q.next.next;
        if (p === q) {
            let h = head;
            while (h != p) {
                p = p.next;
                h = h.next;
            }
            return p;
        }
    }
    return null;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmerge-two-sorted-listsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/merge-two-sorted-lists/description/?envType=study-plan-v2&amp;envId=top-100-liked">合并两个有序链表</a></h2><p>两两比较，哪个小哪个放前面。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode *l = new ListNode(), *p = l;
        while (list1 &amp;&amp; list2) {
            if (list1-&gt;val &gt; list2-&gt;val) {
                p-&gt;next = new ListNode(list2-&gt;val);
                list2 = list2-&gt;next;
            } else {
                p-&gt;next = new ListNode(list1-&gt;val);
                list1 = list1-&gt;next;
            }
            p = p-&gt;next;
        }
        p-&gt;next = list1 ? list1 : list2;
        return l-&gt;next;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeTwoLists = function (list1, list2) {
    let l = new ListNode(0), p = l;
    while (list1 &amp;&amp; list2) {
        p.next = new ListNode(Math.min(list1.val, list2.val));
        if (list1.val &lt; list2.val)
            list1 = list1.next;
        else
            list2 = list2.next;
        p = p.next;
    }
    p.next = list1 ? list1 : list2;
    return l.next;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsadd-two-numbersdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/add-two-numbers/description/?envType=study-plan-v2&amp;envId=top-100-liked">两数相加</a></h2><p>节点数值相加然后注意进位就行。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *head = new ListNode(), *p = l1, *q = l2, *l = head;
        int carry = 0;
        while (p || q) {
            int x = carry;
            if (p) {
                x += p-&gt;val;
                p = p-&gt;next;
            }
            if (q) {
                x += q-&gt;val;
                q = q-&gt;next;
            }
            carry = x / 10;
            l-&gt;next = new ListNode(x % 10);
            l = l-&gt;next;
        }
        if (carry)
            l-&gt;next = new ListNode(1);
        return head-&gt;next;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
    let head = new ListNode(0), l = head, carry = 0;
    while (l1 || l2 || carry) {
        let x = carry;
        if (l1) {
            x += l1.val;
            l1 = l1.next;
        }
        if (l2) {
            x += l2.val;
            l2 = l2.next;
        }
        // C++ 写习惯了老是忘了加 Math.floor（T-T）
        carry = Math.floor(x / 10);
        l.next = new ListNode(x % 10);
        l = l.next;
    }
    return head.next;
};
</code></pre>
<h2 id="-n-httpsleetcodecnproblemsremove-nth-node-from-end-of-listdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/?envType=study-plan-v2&amp;envId=top-100-liked">删除链表的倒数第 N 个结点</a></h2><p>先找到倒数第N个节点的位置，删除就改变指针指向。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *p = head, *q = head;
        for (int i = 0; i &lt; n; ++i)
            q = q-&gt;next;
        if (q == nullptr)
            return p-&gt;next;
        q = q-&gt;next;
        while (q) {
            q = q-&gt;next;
            p = p-&gt;next;
        }
        p-&gt;next = p-&gt;next-&gt;next;
        return head;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function (head, n) {
    let p = head, q = head;
    for (let i = 0; i &lt; n; ++i)
        p = p.next;
    if(p === null)
        return q.next;
    while (p &amp;&amp; p.next) {
        p = p.next;
        q = q.next;
    }
    q.next = q.next.next;
    return head;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsswap-nodes-in-pairsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/swap-nodes-in-pairs/description/?envType=study-plan-v2&amp;envId=top-100-liked">两两交换链表中的节点</a></h2><p>假如链表是<code>1-&gt;2-&gt;3-&gt;4</code>，放一个哨兵节点变成<code>0-&gt;1-&gt;2-&gt;3-&gt;4</code>，从这个节点开始<code>node1-&gt;0</code>、<code>node2-&gt;1</code>，每次取<code>node3</code>为<code>node2</code>的下一个节点（这里是<code>2</code>），<code>node2</code>和<code>node3</code>是真正要交换的节点。更改<code>node2</code>下一个节点指向<code>node3</code>的下一个节点（<code>1-&gt;3</code>），而<code>node3</code>下一个节点指向<code>node2</code>（<code>2-&gt;1</code>），再修改<code>node1</code>下一个节点指向<code>node3</code>(<code>0-&gt;2</code>)，这样就连起来了<code>0-&gt;2-&gt;1-&gt;3-&gt;4</code>，成功交换了<code>1 (node2)</code>和<code>2 (node3)</code>的顺序，之后更新<code>node1</code>和<code>node2</code>为下一次循环做准备（<code>node1</code>移到<code>1</code>，<code>node2</code>移到<code>3</code>）。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *dummy = new ListNode(0, head), *node1 = dummy, *node2 = head;
        while (node2 &amp;&amp; node2-&gt;next) {
            ListNode* node3 = node2-&gt;next;
            node2-&gt;next = node3-&gt;next;
            node3-&gt;next = node2;
            node1-&gt;next = node3;
            node1 = node2;
            node2 = node2-&gt;next;
        }
        return dummy-&gt;next;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function (head) {
    let dummy = new ListNode(0, head), node1 = dummy, node2 = head;
    while (node2 &amp;&amp; node2.next) {
        let node3 = node2.next;
        node2.next = node3.next;
        node3.next = node2;
        node1.next = node3;
        node1 = node2;
        node2 = node2.next;
    }
    return dummy.next;
};
</code></pre>
<h2 id="k-httpsleetcodecnproblemsreverse-nodes-in-k-groupdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/reverse-nodes-in-k-group/description/?envType=study-plan-v2&amp;envId=top-100-liked">K 个一组翻转链表</a></h2><p>主要思考的就是一组翻转后怎么前后连起来。我们需要用到的指针有，这组链表翻转前的前一个节点和后一个节点，以及这组链表翻转后的头节点和尾节点。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        int n = 0;
        for (ListNode* cur = head; cur; cur = cur-&gt;next)
            n++;
        ListNode *dummy = new ListNode(0, head), *l = dummy, *pre = nullptr,
                 *cur = head;
        for (; n &gt;= k; n -= k) {
            for (int i = 0; i &lt; k; ++i) {
                ListNode* nxt = cur-&gt;next;
                cur-&gt;next = pre;
                pre = cur;
                cur = nxt;
            }
            ListNode* nxt = l-&gt;next;
            nxt-&gt;next = cur;
            l-&gt;next = pre;
            l = nxt;
        }
        return dummy-&gt;next;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function (head, k) {
    let i = 0, dummy = new ListNode(0, head), p = head, q = dummy, pre = null, cur = head, nxt = head;
    while(p) {
        i++;
        p = p.next;
        if(i % k == 0) {
            while(nxt !== p) {
                nxt = cur.next;
                cur.next = pre;
                pre = cur;
                cur = nxt;
            }
            nxt = q.next;
            nxt.next = cur;
            q.next = pre;
            q = nxt;
        }
    }
    return dummy.next;
};
</code></pre>
<h2 id=""><a href="">随机链表的复制</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id=""><a href="">排序链表</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id="-k-httpsleetcodecnproblemsmerge-k-sorted-listsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/merge-k-sorted-lists/description/?envType=study-plan-v2&amp;envId=top-100-liked">合并 K 个升序链表</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector&lt;ListNode*&gt;&amp; lists) {
        auto cmp = [](ListNode* a, ListNode* b) { return a-&gt;val &gt; b-&gt;val; };
        priority_queue&lt;ListNode*, vector&lt;ListNode*&gt;, decltype(cmp)&gt; heap(cmp);
        for (auto list : lists)
            if (list)
                heap.push(list);
        ListNode *head = new ListNode(0), *p = head;
        while (!heap.empty()) {
            ListNode* q = heap.top();
            heap.pop();
            p-&gt;next = q;
            p = p-&gt;next;
            if (q-&gt;next)
                heap.push(q-&gt;next);
        }
        return head-&gt;next;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function (lists) {
    let n = lists.length;
    if (n === 0)
        return null;
    function mergeTwoLists(list1, list2) {
        const dummy = new ListNode();
        let p = dummy;
        while (list1 &amp;&amp; list2) {
            if (list1.val &gt;= list2.val) {
                p.next = list2;
                list2 = list2.next;
            } else {
                p.next = list1;
                list1 = list1.next;
            }
            p = p.next;
        }
        p.next = list1 ? list1 : list2;
        return dummy.next;
    };
    for (let i = 1; i &lt; n; i *= 2) {
        for (let j = 0; j &lt; n - i; j += i * 2)
            lists[j] = mergeTwoLists(lists[j], lists[j + i]);
    }
    return lists[0];
};
</code></pre>
<h2 id="lru-"><a href="">LRU 缓存</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id="httpsleetcodecnproblemsbinary-tree-inorder-traversaldescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/binary-tree-inorder-traversal/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树的中序遍历</a></h2><p>深搜 dfs 啦，先遍历左子树，获取根节点值，再遍历右子树。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector&lt;int&gt; inorderTraversal(TreeNode* root) {
        vector&lt;int&gt; v;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, TreeNode* node) {
            if(node == nullptr)
                return;
            dfs(node-&gt;left);
            v.push_back(node-&gt;val);
            dfs(node-&gt;right);
        };
        dfs(root);
        return v;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function (root) {
    let ans = [];
    function dfs(node) {
        if (node === null)
            return;
        dfs(node.left);
        ans.push(node.val);
        dfs(node.right);
    }
    dfs(root);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmaximum-depth-of-binary-treedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/maximum-depth-of-binary-tree/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树的最大深度</a></h2><p>就是递归思想，找二叉树最大深度的话取左右子树最大的那个就行。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr)
            return 0;
        return max(maxDepth(root-&gt;left), maxDepth(root-&gt;right)) + 1;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function (root) {
    if (root === null)
        return 0;
    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsinvert-binary-treedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/invert-binary-tree/description/?envType=study-plan-v2&amp;envId=top-100-liked">翻转二叉树</a></h2><p>同样是递归思想，互换左右子树。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == nullptr)
            return nullptr;
        TreeNode* left = invertTree(root-&gt;left);
        TreeNode* right = invertTree(root-&gt;right);
        root-&gt;left = right;
        root-&gt;right = left;
        return root;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function (root) {
    if (root === null)
        return null;
    let left = invertTree(root.left);
    let right = invertTree(root.right);
    root.left = right;
    root.right = left;
    return root;
};
</code></pre>
<h2 id="httpsleetcodecnproblemssymmetric-treeenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/symmetric-tree/?envType=study-plan-v2&amp;envId=top-100-liked">对称二叉树</a></h2><p>对称说明左子树的左子树与右子树的右子树相同，左子树的右子树与右子树的左子树相同。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        auto dfs = [&amp;](this auto&amp;&amp; dfs, TreeNode* l, TreeNode* r) -&gt; bool {
            if (l == nullptr)
                return r == nullptr;
            if (r == nullptr)
                return l == nullptr;
            if (l-&gt;val != r-&gt;val)
                return false;
            return dfs(l-&gt;left, r-&gt;right) &amp;&amp; dfs(l-&gt;right, r-&gt;left);
        };
        return dfs(root-&gt;left, root-&gt;right);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function (root) {
    function dfs(l, r) {
        if (l === null &amp;&amp; r === null)
            return true;
        if (l === null || r === null || l.val !== r.val)
            return false;
        return dfs(l.left, r.right) &amp;&amp; dfs(l.right, r.left);
    }
    return dfs(root.left, root.right);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsdiameter-of-binary-treedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/diameter-of-binary-tree/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树的直径</a></h2><p>二叉树的直径是指树中任意两个节点之间最长路径的长度，其实就是树中某个节点的左子树最大深度与右子树最大深度之和的最大值。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    int diameterOfBinaryTree(TreeNode* root) {
        int ans = 0;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, TreeNode* node) -&gt; int {
            if (node == nullptr)
                return -1;
            int left = dfs(node-&gt;left) + 1;
            int right = dfs(node-&gt;right) + 1;
            ans = max(ans, left + right);
            return max(left, right);
        };
        dfs(root);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var diameterOfBinaryTree = function (root) {
    let ans = 0;
    function dfs(node) {
        if (node === null)
            return -1;
        let l = dfs(node.left) + 1, r = dfs(node.right) + 1;
        ans = Math.max(ans, l + r);
        return Math.max(l, r);
    }
    dfs(root);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsbinary-tree-level-order-traversaldescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/binary-tree-level-order-traversal/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树的层序遍历</a></h2><p>广搜用队列。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; levelOrder(TreeNode* root) {
        if (root == nullptr)
            return {};
        vector&lt;vector&lt;int&gt;&gt; ans;
        vector&lt;int&gt; v;
        queue&lt;TreeNode*&gt; q, p;
        q.push(root);
        while (!q.empty()) {
            TreeNode* node = q.front();
            v.push_back(node-&gt;val);
            q.pop();
            if (node-&gt;left)
                p.push(node-&gt;left);
            if (node-&gt;right)
                p.push(node-&gt;right);
            if (q.empty() &amp;&amp; !p.empty()) {
                q = p;
                ans.push_back(v);
                v.clear();
                p = queue&lt;TreeNode*&gt;();
            }
        }
        ans.push_back(v);

        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function (root) {
    if (root === null)
        return [];
    let ans = [];
    const q = new Queue();
    q.enqueue(root);
    while (!q.isEmpty()) {
        let n = q.size(), level = [];
        while (n--) {
            let node = q.dequeue();
            level.push(node.val);
            if (node.left)
                q.enqueue(node.left);
            if (node.right)
                q.enqueue(node.right);
        }
        ans.push(level);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsconvert-sorted-array-to-binary-search-treedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/description/?envType=study-plan-v2&amp;envId=top-100-liked">将有序数组转换为二叉搜索树</a></h2><p>平衡二叉树就是树中任意节点的左右子树高度差绝对值不超过1，我们可以用分治法，左子树取数组前一半，右子树取数组后一半。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* sortedArrayToBST(vector&lt;int&gt;&amp; nums) {
        auto helper = [&amp;](this auto&amp;&amp; helper, int i, int j) -&gt; TreeNode* {
            if (i &gt;= j)
                return nullptr;
            int mid = (i + j) / 2;
            TreeNode* node = new TreeNode(nums[mid]);
            node-&gt;left = helper(i, mid);
            node-&gt;right = helper(mid + 1, j);
            return node;
        };
        return helper(0, nums.size());
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} nums
 * @return {TreeNode}
 */
var sortedArrayToBST = function (nums) {
    function arrayToBST(l, r) {
        let len = r - l;
        if (len === 0)
            return null;
        let mid = Math.floor(len / 2) + l;
        return new TreeNode(nums[mid], sortedArrayToBST(nums.slice(l, mid)), sortedArrayToBST(nums.slice(mid + 1, r)));
    }
    return arrayToBST(0, nums.length);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsvalidate-binary-search-treedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/validate-binary-search-tree/description/?envType=study-plan-v2&amp;envId=top-100-liked">验证二叉搜索树</a></h2><p>二叉搜索数如果用中序遍历展开成数组，那么这个数组一定是从小到大的。要验证是否是<code>BST</code>，只要比较相邻两个元素的大小就行了，可以用一个变量保存前一个元素的值。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    bool isValidBST(TreeNode* root, long long l = LONG_MAX,
                    long long r = LONG_MIN) {
        if (root == nullptr)
            return true;
        long long val = root-&gt;val;
        if (val &gt;= l || val &lt;= r)
            return false;
        return isValidBST(root-&gt;left, val, r) &amp;&amp;
               isValidBST(root-&gt;right, l, val);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function (root) {
    let pre = -Infinity;
    function dfs(node) {
        if (node === null)
            return true;
        if (!dfs(node.left))
            return false;
        if (node.val &lt;= pre)
            return false;
        pre = node.val;
        return dfs(node.right);
    }
    return dfs(root);
};
</code></pre>
<h2 id="-k-httpsleetcodecnproblemskth-smallest-element-in-a-bstenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/kth-smallest-element-in-a-bst/?envType=study-plan-v2&amp;envId=top-100-liked">二叉搜索树中第 K 小的元素</a></h2><p>中序遍历，先递归左子树，判断<code>k</code>值以决定是否返回当前节点值，再递归右子树。<code>C++</code>注意参数<code>k</code>要引用传递，这样所有递归层共享一个<code>k</code>，找到答案（<code>k=0</code>时）就返回。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    int kthSmallest(TreeNode* root, int &amp;k) {
        if (root == nullptr)
            return -1;
        int l = kthSmallest(root-&gt;left, k);
        if (l != -1)
            return l;
        if (--k == 0)
            return root-&gt;val;
        return kthSmallest(root-&gt;right, k);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} k
 * @return {number}
 */
var kthSmallest = function (root, k) {
    function dfs(node) {
        if (node === null)
            return -1;
        let l = dfs(node.left, k);
        if (l !== -1)
            return l;
        if (--k === 0)
            return node.val;
        return dfs(node.right, k);
    }
    return dfs(root);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsbinary-tree-right-side-viewenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/binary-tree-right-side-view/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树的右视图</a></h2><p>用队列实现层序遍历，保存每层最后一个元素就行。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    vector&lt;int&gt; rightSideView(TreeNode* root) {
        if(root == nullptr)
            return {};
        vector&lt;int&gt; v;
        queue&lt;TreeNode*&gt; q;
        q.push(root);
        while (!q.empty()) {
            int n = q.size();
            for (int i = 0; i &lt; n; ++i) {
                TreeNode* node = q.front();
                q.pop();
                if (node-&gt;left)
                    q.push(node-&gt;left);
                if (node-&gt;right)
                    q.push(node-&gt;right);
                if (i == n - 1)
                    v.push_back(node-&gt;val);
            }
        }
        return v;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var rightSideView = function (root) {
    let ans = [];
    if (root === null)
        return ans;
    const q = new Queue();
    q.enqueue(root);
    while (!q.isEmpty()) {
        let n = q.size();
        while (n--) {
            let node = q.dequeue();
            if (n == 0)
                ans.push(node.val);
            if (node.left)
                q.enqueue(node.left);
            if (node.right)
                q.enqueue(node.right);
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsflatten-binary-tree-to-linked-listdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树展开为链表</a></h2><p><code>dfs</code>，先递归右子树，再递归左子树，最后处理当前节点。用<code>head</code>记录已展开链表的前驱节点。处理每个节点时，将其<code>right</code>指向<code>head</code>，<code>left</code>置空，然后更新<code>head</code>为当前节点。这样从后往前构建，最终得到前序遍历顺序的链表。</p>
<pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
    TreeNode* head;

public:
    void flatten(TreeNode* root) {
        if (root == nullptr)
            return;
        flatten(root-&gt;right);
        flatten(root-&gt;left);
        root-&gt;left = nullptr;
        root-&gt;right = head;
        head = root;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {void} Do not return anything, modify root in-place instead.
 */
var flatten = function(root) {
    let head = null;    
    function dfs(node) {
        if(node === null)
            return;
        dfs(node.right);
        dfs(node.left);
        node.left = null;
        node.right = head;
        head = node;
    }
    dfs(root);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsconstruct-binary-tree-from-preorder-and-inorder-traversaldescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/?envType=study-plan-v2&amp;envId=top-100-liked">从前序与中序遍历序列构造二叉树</a></h2><p>前序遍历是根-左-右，中序遍历是左-根-右，我们只要找到前序遍历的根在中序遍历数组里的位置，就知道了这个位置左边是左子树，右边是右子树，并且能够知道左右子树的大小，以在前序遍历数组进行划分，递归构建树。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector&lt;int&gt;&amp; preorder, vector&lt;int&gt;&amp; inorder) {
        int n = preorder.size();
        unordered_map&lt;int, int&gt; index;
        for (int i = 0; i &lt; n; i++)
            index[inorder[i]] = i;
        function&lt;TreeNode*(int, int, int, int)&gt; dfs =
            [&amp;](int pre_l, int pre_r, int in_l, int in_r) -&gt; TreeNode* {
            if (pre_l == pre_r)
                return nullptr;
            int left_size = index[preorder[pre_l]] - in_l;
            TreeNode* left =
                dfs(pre_l + 1, pre_l + 1 + left_size, in_l, in_l + left_size);
            TreeNode* right =
                dfs(pre_l + 1 + left_size, pre_r, in_l + 1 + left_size, in_r);
            return new TreeNode(preorder[pre_l], left, right);
        };
        return dfs(0, n, 0, n);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function (preorder, inorder) {
    function dfs(pre_l, pre_r, in_l, in_r) {
        if (pre_l &gt;= pre_r)
            return null;
        let pos = inorder.indexOf(preorder[pre_l]);
        let root = new TreeNode(preorder[pre_l]);
        root.left = dfs(pre_l + 1, pre_l + 1 + pos - in_l, in_l, pos);
        root.right = dfs(pre_l + 1 + pos - in_l, pre_r, pos + 1, in_r);
        return root;
    }
    return dfs(0, preorder.length, 0, inorder.length);
};
// var buildTree = function (preorder, inorder) {
//     if (preorder.length === 0 || inorder.length === 0)
//         return null;
//     let root = new TreeNode(preorder[0]);
//     let pos = inorder.indexOf(preorder[0]);
//     root.left = buildTree(preorder.slice(1, pos + 1), inorder.slice(0, pos));
//     root.right = buildTree(preorder.slice(pos + 1, preorder.length), inorder.slice(pos + 1, inorder.length));
//     return root;
// };
</code></pre>
<h2 id="-iiihttpsleetcodecnproblemspath-sum-iiidescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/path-sum-iii/description/?envType=study-plan-v2&amp;envId=top-100-liked">路径总和 III</a></h2><p>暴力做法是枚举所有路径的和。更好的做法是用哈希表保存前缀和（从根节点开始的路径），假如当前路径和是<code>sum</code>，每次答案加上哈希表里<code>sum - targetSum</code>的值就行了。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    int pathSum(TreeNode* root, int targetSum) {
        int ans = 0;
        if(root == nullptr)
            return 0;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, TreeNode* node, long sum) {
            if (node == nullptr)
                return;
            sum += node-&gt;val;
            ans += sum == targetSum;
            dfs(node-&gt;left, sum);
            dfs(node-&gt;right, sum);
        };
        dfs(root, 0);
        return ans + pathSum(root-&gt;left, targetSum) +
               pathSum(root-&gt;right, targetSum);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {number}
 */
var pathSum = function (root, targetSum) {
    let ans = 0;
    const cnt = new Map();
    cnt.set(0, 1);
    function dfs(node, sum) {
        if (node === null)
            return;
        sum += node.val;
        ans += cnt.get(sum - targetSum) ?? 0;
        cnt.set(sum, (cnt.get(sum) ?? 0) + 1);
        dfs(node.left, sum);
        dfs(node.right, sum);
        cnt.set(sum, cnt.get(sum) - 1);
    }
    dfs(root, 0);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemslowest-common-ancestor-of-a-binary-treedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树的最近公共祖先</a></h2><p>深搜，递归边界条件是什么？节点为空这个容易想到，还有就是节点为<code>p</code>或<code>q</code>时，不用再往下找了，直接返回。递归左、右子树，如果都有返回，说明找到了公共祖先。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == NULL || root == p || root == q)
            return root;
        TreeNode* left = lowestCommonAncestor(root-&gt;left, p, q);
        TreeNode* right = lowestCommonAncestor(root-&gt;right, p, q);
        if (left &amp;&amp; right)
            return root;
        if (left)
            return left;
        return right;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    if(root === null || root === p || root === q)
        return root;
    const left = lowestCommonAncestor(root.left, p, q);
    const right = lowestCommonAncestor(root.right, p, q);
    if(left &amp;&amp; right)
        return root;
    return left ?? right;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsbinary-tree-maximum-path-sumdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/binary-tree-maximum-path-sum/description/?envType=study-plan-v2&amp;envId=top-100-liked">二叉树中的最大路径和</a></h2><p>二叉树中的最大路径和就是任意节点左子树最大链和与右子树最大链和与当前节点值之和的最大值，如果左/右子树节点值之和是负数，就取0。递归返回当前子树最大链和，如果把左右子树链和都返回，路径会分叉，所以要取最大。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    int maxPathSum(TreeNode* root) {
        int res = INT_MIN;
        auto dfs = [&amp;](auto dfs, TreeNode* node) -&gt; int {
            if (node == nullptr)
                return 0;
            int left = max(0, dfs(dfs, node-&gt;left));
            int right = max(0, dfs(dfs, node-&gt;right));
            res = max(res, left + right + node-&gt;val);
            return max(left, right) + node-&gt;val;
        };
        dfs(dfs, root);
        return res;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxPathSum = function (root) {
    let ans = -Infinity;
    function dfs(node) {
        if (node === null)
            return 0;
        let l = Math.max(0, dfs(node.left));
        let r = Math.max(0, dfs(node.right));
        ans = Math.max(ans, l + r + node.val);
        return Math.max(l, r) + node.val;
    }
    dfs(root);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsnumber-of-islandsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/number-of-islands/description/?envType=study-plan-v2&amp;envId=top-100-liked">岛屿数量</a></h2><p><code>dfs</code>和<code>bfs</code>都可以，<code>dfs</code>写起来更简洁一些，主要就是标记访问已经访问过的陆地格子。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int numIslands(vector&lt;vector&lt;char&gt;&gt;&amp; grid) {
        int m = (int)grid.size(), n = (int)grid[0].size(), ans = 0;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i, int j) {
            if (i &lt; 0 || j &lt; 0 || i &gt;= m || j &gt;= n || grid[i][j] != &#x27;1&#x27;)
                return;
            grid[i][j] = &#x27;2&#x27;;
            dfs(i + 1, j);
            dfs(i - 1, j);
            dfs(i, j - 1);
            dfs(i, j + 1);
        };
        for (int i = 0; i &lt; m; ++i) {
            for (int j = 0; j &lt; n; ++j) {
                if (grid[i][j] == &#x27;1&#x27;) {
                    dfs(i, j);
                    ans++;
                }
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {character[][]} grid
 * @return {number}
 */
var numIslands = function (grid) {
    let m = grid.length, n = grid[0].length, ans = 0;
    let visited = Array.from({ length: m }, () =&gt; new Array(n).fill(false));
    let dirs = [[0, 1], [1, 0], [-1, 0], [0, -1]];
    function bfs(i, j) {
        const q = new Queue();
        q.enqueue({ x: i, y: j });
        while (!q.isEmpty()) {
            const { x, y } = q.dequeue();
            visited[x][y] = true;
            for (const dir of dirs) {
                const a = dir[0] + x, b = dir[1] + y;
                if (a &gt;= 0 &amp;&amp; b &gt;= 0 &amp;&amp; a &lt; m &amp;&amp; b &lt; n &amp;&amp; !visited[a][b] &amp;&amp; grid[a][b] === &#x27;1&#x27;) {
                    visited[a][b] = true;
                    q.enqueue({ x: a, y: b });
                }
            }
        }
    }
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (grid[i][j] === &#x27;1&#x27; &amp;&amp; !visited[i][j]) {
                bfs(i, j);
                ++ans;
            }
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsrotting-orangesdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/rotting-oranges/description/?envType=study-plan-v2&amp;envId=top-100-liked">腐烂的橘子</a></h2><p><code>bfs</code>，初始将所有腐烂的橘子加入队列，模拟橘子腐烂的过程，如果四周有新鲜橘子，就把这些橘子腐烂掉，更新队列。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int orangesRotting(vector&lt;vector&lt;int&gt;&gt;&amp; grid) {
        using pii = pair&lt;int, int&gt;;
        int ans = 0, m = grid.size(), n = grid[0].size();
        queue&lt;pii&gt; q, tmp;
        for (int i = 0; i &lt; m; ++i) {
            for (int j = 0; j &lt; n; ++j) {
                if (grid[i][j] == 2)
                    q.push({i, j});
            }
        }
        vector&lt;pii&gt; dirs{{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
        while (!q.empty()) {
            int x = q.front().first, y = q.front().second;
            grid[x][y] = 0;
            q.pop();
            for (int i = 0; i &lt; 4; ++i) {
                int nxt_x = dirs[i].first + x;
                int nxt_y = dirs[i].second + y;
                if (nxt_x &gt;= 0 &amp;&amp; nxt_x &lt; m &amp;&amp; nxt_y &gt;= 0 &amp;&amp; nxt_y &lt; n &amp;&amp;
                    grid[nxt_x][nxt_y] == 1) {
                    grid[nxt_x][nxt_y] = 0;
                    tmp.push({nxt_x, nxt_y});
                }
            }
            if (q.empty() &amp;&amp; !tmp.empty()) {
                ++ans;
                q = tmp;
                tmp = queue&lt;pii&gt;();
            }
        }
        for (auto v : grid) {
            for (auto x : v) {
                if (x)
                    return -1;
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} grid
 * @return {number}
 */
var orangesRotting = function (grid) {
    let m = grid.length, n = grid[0].length;
    let ans = 0, fresh = 0;
    let dirs = [[0, 1], [1, 0], [-1, 0], [0, -1]];
    let q = [];
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (grid[i][j] === 2)
                q.push([i, j]);
            else if (grid[i][j] === 1)
                fresh++;
        }
    }
    while (q.length &amp;&amp; fresh) {
        ++ans;
        const tmp = q;
        q = [];
        for (const [x, y] of tmp) {
            for (const dir of dirs) {
                let i = x + dir[0], j = y + dir[1];
                if (i &gt;= 0 &amp;&amp; j &gt;= 0 &amp;&amp; i &lt; m &amp;&amp; j &lt; n &amp;&amp; grid[i][j] === 1) {
                    fresh--;
                    grid[i][j] = 2;
                    q.push([i, j]);
                }
            }
        }
    }
    return fresh ? -1 : ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemscourse-scheduledescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/course-schedule/description/?envType=study-plan-v2&amp;envId=top-100-liked">课程表</a></h2><p>构建有向图，记录每个节点的入度。将入度为<code>0</code>的课程（也就是没有先修要求的课）加入队列，开始BFS（比较像这个过程），逐个出列，减小相关课的入度，如果入度变为<code>0</code>了，加入队列，直到没有入度为<code>0</code>的课可入列。记录入列的顶点个数，最后判断是否等于总课程数。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool canFinish(int numCourses, vector&lt;vector&lt;int&gt;&gt;&amp; prerequisites) {
        map&lt;int, vector&lt;int&gt;&gt; graph;
        vector&lt;int&gt; inDegrees(numCourses);
        for (auto prereq : prerequisites) {
            graph[prereq[1]].push_back(prereq[0]);
            ++inDegrees[prereq[0]];
        }
        queue&lt;int&gt; q;
        for (int i = 0; i &lt; numCourses; ++i)
            if (inDegrees[i] == 0)
                q.push(i);
        if (q.size() == 0)
            return false;
        int cnt = 0;
        while (!q.empty()) {
            int course = q.front();
            q.pop();
            ++cnt;
            for (auto nxt : graph[course]) {
                --inDegrees[nxt];
                if (inDegrees[nxt] == 0)
                    q.push(nxt);
            }
        }
        return cnt == numCourses;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {boolean}
 */
var canFinish = function (numCourses, prerequisites) {
    const graph = Array.from({ length: numCourses }, () =&gt; []);
    const indegrees = new Array(numCourses).fill(0);
    for (const prerequisite of prerequisites) {
        graph[prerequisite[1]].push(prerequisite[0]);
        indegrees[prerequisite[0]]++;
    }
    const q = new Queue();
    for (let i = 0; i &lt; numCourses; ++i) {
        if (indegrees[i] === 0)
            q.enqueue(i);
    }
    let cnt = 0;
    while (!q.isEmpty()) {
        const course = q.dequeue();
        cnt++;
        for (const nxtCourse of graph[course]) {
            indegrees[nxtCourse]--;
            if (indegrees[nxtCourse] === 0)
                q.enqueue(nxtCourse);
        }
    }
    return cnt === numCourses;
};
</code></pre>
<h2 id="-trie-"><a href="">实现 Trie (前缀树)</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id="httpsleetcodecnproblemspermutationsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/permutations/description/?envType=study-plan-v2&amp;envId=top-100-liked">全排列</a></h2><p>回溯需要思考的就是1. 递归边界条件，2. 哪些参数需要更新给子问题，3. 什么时候回退。深度优先搜索所有排列方案，枚举第<code>1</code>位元素一直到第<code>n</code>位，每次枚举的元素要保证没取过。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; permute(vector&lt;int&gt;&amp; nums) {
        vector&lt;vector&lt;int&gt;&gt; ans;
        int n = nums.size();
        vector&lt;int&gt; path(n), on_path(n);
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i) {
            if (i == n) {
                ans.push_back(path);
                return;
            }
            for (int j = 0; j &lt; n; ++j) {
                if (!on_path[j]) {
                    path[i] = nums[j];
                    on_path[j] = 1;
                    dfs(i + 1);
                    on_path[j] = 0;
                }
            }
        };
        dfs(0);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function (nums) {
    const n = nums.length;
    const path = Array(n).fill(0);
    const onPath = Array(n).fill(false);
    const ans = [];
    const dfs = (i) =&gt; {
        if (i === n) {
            ans.push(path.slice());
            return;
        }
        for (let j = 0; j &lt; n; ++j) {
            if (!onPath[j]) {
                path[i] = nums[j];
                onPath[j] = true;
                dfs(i + 1);
                onPath[j] = false;
            }
        }
    }
    dfs(0);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemssubsetsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/subsets/description/?envType=study-plan-v2&amp;envId=top-100-liked">子集</a></h2><p>选或不选，将当前元素加入路径递归结束后恢复现场。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; subsets(vector&lt;int&gt;&amp; nums) {
        vector&lt;vector&lt;int&gt;&gt; ans;
        vector&lt;int&gt; v;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i) {
            if (i == nums.size()) {
                ans.push_back(v);
                return;
            }
            v.push_back(nums[i]);
            dfs(i + 1);
            v.pop_back();
            dfs(i + 1);
        };
        dfs(0);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function (nums) {
    let ans = [], v = [];
    function dfs(i) {
        if (i === nums.length) {
            ans.push([...v]);
            return;
        }
        dfs(i + 1);
        v.push(nums[i]);
        dfs(i + 1);
        v.pop();
    }
    dfs(0);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsletter-combinations-of-a-phone-numberdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/?envType=study-plan-v2&amp;envId=top-100-liked">电话号码的字母组合</a></h2><p>回溯，每次加上一个字母，递归终止条件是字符串长度与按键长度相等。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;string&gt; letterCombinations(string digits) {
        vector&lt;string&gt; v{&quot;&quot;,&quot;abc&quot;,  &quot;def&quot;, &quot;ghi&quot;, &quot;jkl&quot;, &quot;mno&quot;, &quot;pqrs&quot;, &quot;tuv&quot;, &quot;wxyz&quot;};
        vector&lt;string&gt; ans;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, string s, int i) {
            if (i == digits.size()) {
                ans.push_back(s);
                return;
            }
            for (auto x : v[digits[i] - &#x27;1&#x27;])
                dfs(s + x, i + 1);
        };
        dfs(&quot;&quot;, 0);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function (digits) {
    const phone = [&quot;&quot;, &quot;&quot;, &quot;abc&quot;, &quot;def&quot;, &quot;ghi&quot;, &quot;jkl&quot;, &quot;mno&quot;, &quot;pqrs&quot;, &quot;tuv&quot;, &quot;wxyz&quot;];
    let ans = [];
    function dfs(i, s) {
        if (i === digits.length) {
            ans.push(s);
            return;
        }
        for (const x of phone[digits[i]])
            dfs(i + 1, s + x)
    }
    dfs(0, &quot;&quot;);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemscombination-sumdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/combination-sum/description/?envType=study-plan-v2&amp;envId=top-100-liked">组合总和</a></h2><p>如果不选<code>candidates[i]</code>，递归到<code>dfs(i+1,sum)</code>，如果选<code>candidates[i]</code>，递归到<code>dfs(i,sum+candidates[i])</code>。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; combinationSum(vector&lt;int&gt;&amp; candidates, int target) {
        vector&lt;vector&lt;int&gt;&gt; ans;
        vector&lt;int&gt; v;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i, int sum) {
            if (i &gt;= candidates.size() || sum &gt; target)
                return;
            if (sum == target) {
                ans.push_back(v);
                return;
            }
            v.push_back(candidates[i]);
            dfs(i, sum + candidates[i]);
            v.pop_back();
            dfs(i + 1, sum);
        };
        dfs(0, 0);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum = function (candidates, target) {
    let ans = [];
    function dfs(i, sum, v) {
        if (sum &gt; target || i === candidates.length)
            return;
        if (sum === target) {
            ans.push([...v]);
            return;
        }
        v.push(candidates[i]);
        dfs(i, sum + candidates[i], v);
        v.pop();
        dfs(i + 1, sum, v);
    }
    dfs(0, 0, []);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsgenerate-parenthesesdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/generate-parentheses/description/?envType=study-plan-v2&amp;envId=top-100-liked">括号生成</a></h2><p>如果右括号数量少于左括号数量，加上右括号继续递归；如果左括号数量少于<code>n</code>，加上左括号继续递归。当字符串长度达到<code>2*n</code>时，递归终止。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;string&gt; generateParenthesis(int n) {
        vector&lt;string&gt; ans;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int l, int r, string s) {
            if (s.size() == 2 * n) {
                ans.push_back(s);
                return;
            }
            if (r &lt; l)
                dfs(l, r + 1, s + &#x27;)&#x27;);
            if (l &lt; n)
                dfs(l + 1, r, s + &#x27;(&#x27;);
        };
        dfs(0, 0, &quot;&quot;);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function (n) {
    let ans = [];
    function dfs(l, r, s) {
        if (s.length === 2 * n) {
            ans.push(s);
            return;
        }
        if (l &gt; r)
            dfs(l, r + 1, s + &#x27;)&#x27;);
        if (l &lt; n)
            dfs(l + 1, r, s + &#x27;(&#x27;);
    }
    dfs(0, 0, &quot;&quot;);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsword-searchdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/word-search/description/?envType=study-plan-v2&amp;envId=top-100-liked">单词搜索</a></h2><p>如果<code>board[i][j]</code>与<code>word</code>第一个字符相同，就进行深搜，枚举<code>(i,j)</code>周围的四个相邻格子<code>(x,y)</code>，如果<code>(x,y)</code>没有出界并且与<code>word</code>下一个字符匹配，就继续递归，递归边界条件是匹配到<code>word</code>最后一个字符。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool exist(vector&lt;vector&lt;char&gt;&gt;&amp; board, string word) {
        int m = board.size(), n = board[0].size();
        vector&lt;pair&lt;int, int&gt;&gt; dirs{{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i, int j, int k) {
            if (k == word.size() - 1)
                return true;
            board[i][j] = 0;
            for (auto dir : dirs) {
                int x = i + dir.first, y = j + dir.second;
                if (x &gt;= 0 &amp;&amp; y &gt;= 0 &amp;&amp; x &lt; m &amp;&amp; y &lt; n &amp;&amp;
                    board[x][y] == word[k + 1] &amp;&amp; dfs(x, y, k + 1))
                    return true;
            }
            board[i][j] = word[k];
            return false;
        };
        for (int i = 0; i &lt; m; ++i) {
            for (int j = 0; j &lt; n; ++j) {
                if (board[i][j] == word[0] &amp;&amp; dfs(i, j, 0))
                    return true;
            }
        }
        return false;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function (board, word) {
    let m = board.length, n = board[0].length;
    let dirs = [[1, 0], [0, 1], [-1, 0], [0, -1]];
    function dfs(i, j, k) {
        if (k === word.length - 1)
            return true;
        let res = false;
        board[i][j] = &#x27;0&#x27;;
        for (const dir of dirs) {
            let x = dir[0] + i, y = dir[1] + j;
            if (x &gt;= 0 &amp;&amp; y &gt;= 0 &amp;&amp; x &lt; m &amp;&amp; y &lt; n &amp;&amp; board[x][y] === word[k + 1])
                res |= dfs(x, y, k + 1);
        }
        board[i][j] = word[k];
        return res;
    }
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (board[i][j] === word[0] &amp;&amp; dfs(i, j, 0))
                return true;
        }
    }
    return false;
};
</code></pre>
<h2 id="httpsleetcodecnproblemspalindrome-partitioningdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/palindrome-partitioning/description/?envType=study-plan-v2&amp;envId=top-100-liked">分割回文串</a></h2><p>回溯的基本思想：选或不选当前元素，<code>s[i]</code>作为当前子串的最后一个字符，<code>s[i+1]</code>作为下一个子串的第一个字符（在当前子串是回文串的前提下选，否则不选）。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;string&gt;&gt; partition(string s) {
        vector&lt;vector&lt;string&gt;&gt; ans;
        auto isPalindrome = [&amp;](string t) {
            for (int i = 0; i &lt;= t.size() / 2; ++i)
                if (t[i] != t[t.size() - 1 - i])
                    return false;
            return true;
        };
        vector&lt;string&gt; v;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i) {
            if (i == s.size()) {
                ans.push_back(v);
                return;
            }
            for (int j = i; j &lt; s.size(); ++j) {
                string t = s.substr(i, j - i + 1);
                if (isPalindrome(t)) {
                    v.push_back(t);
                    dfs(j + 1);
                    v.pop_back();
                }
            }
        };
        dfs(0);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {string[][]}
 */
var partition = function (s) {
    let ans = [], v = [];
    function isPalindrome(t) {
        let left = 0, right = t.length - 1;
        while (left &lt; right) {
            if (t[left] !== t[right])
                return false;
            left++;
            right--;
        }
        return true;
    }
    function dfs(i, j) {
        if (j === s.length) {
            ans.push([...v]);
            return;
        }
        if (j &lt; s.length - 1)
            dfs(i, j + 1);
        let t = s.slice(i, j + 1);
        if (isPalindrome(t)) {
            v.push(t);
            dfs(j + 1, j + 1);
            v.pop();
        }
    }
    dfs(0, 0);
    return ans;
};
</code></pre>
<h2 id="n-httpsleetcodecnproblemsn-queensdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/n-queens/description/?envType=study-plan-v2&amp;envId=top-100-liked">N 皇后</a></h2><p>哈希集合记录某列/正对角线/副对角线是否放置过皇后，如果都没有，就递归下一行。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;string&gt;&gt; solveNQueens(int n) {
        vector&lt;vector&lt;string&gt;&gt; ans;
        unordered_set&lt;int&gt; col, dia1, dia2;
        vector&lt;string&gt; v;
        auto dfs = [&amp;](this auto&amp;&amp; dfs, int i) {
            if (i == n) {
                ans.push_back(v);
                return;
            }
            for (int j = 0; j &lt; n; ++j) {
                if (col.count(j) || dia1.count(i - j) || dia2.count(i + j))
                    continue;
                col.insert(j);
                dia1.insert(i - j);
                dia2.insert(i + j);
                string s(n, &#x27;.&#x27;);
                s[j] = &#x27;Q&#x27;;
                v.push_back(s);
                dfs(i + 1);
                col.erase(j);
                dia1.erase(i - j);
                dia2.erase(i + j);
                v.pop_back();
            }
        };
        dfs(0);
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function (n) {
    let ans = [], v = [];
    const col = new Set(), diag1 = new Set(), diag2 = new Set();
    function dfs(i) {
        if (i === n) {
            ans.push([...v]);
            return;
        }
        for (let j = 0; j &lt; n; ++j) {
            if (!col.has(j) &amp;&amp; !diag1.has(i + j) &amp;&amp; !diag2.has(i - j)) {
                col.add(j);
                diag1.add(i + j);
                diag2.add(i - j);
                let s = &quot;.&quot;.repeat(j) + &quot;Q&quot; + &quot;.&quot;.repeat(n - j - 1);
                v.push(s);
                dfs(i + 1);
                col.delete(j);
                diag1.delete(i + j);
                diag2.delete(i - j);
                v.pop();
            }
        }
    }
    dfs(0);
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemssearch-insert-positiondescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/search-insert-position/description/?envType=study-plan-v2&amp;envId=top-100-liked">搜索插入位置</a></h2><p>二分查找标准模版～</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int searchInsert(vector&lt;int&gt;&amp; nums, int target) {
        int left = -1, right = nums.size();
        while (left + 1 &lt; right) {
            int mid = left + (right - left) / 2;
            nums[mid] &lt; target ? left++ : right--;
        }
        return right;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function (nums, target) {
    let left = -1, right = nums.length;
    while (left + 1 &lt; right) {
        let mid = Math.floor((left + right) / 2);
        if (nums[mid] &lt; target)
            left = mid;
        else
            right = mid;
    }
    return right;
};
</code></pre>
<h2 id="httpsleetcodecnproblemssearch-a-2d-matrixdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/search-a-2d-matrix/description/?envType=study-plan-v2&amp;envId=top-100-liked">搜索二维矩阵</a></h2><p>已知这个二维矩阵每行中的整数从左到右按非严格递增顺序排列，每行的第一个整数大于前一行的最后一个整数。那么把它(<code>m * n</code>)展开成一维矩阵(<code>1 * mn</code>)，应该是严格递增的，直接二分查找，中间的值对应<code>matrix[mid / n][mid % n]</code>。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool searchMatrix(vector&lt;vector&lt;int&gt;&gt;&amp; matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int left = -1, right = m * n;
        while (left + 1 &lt; right) {
            int mid = (left + right) / 2;
            int x = matrix[mid / n][mid % n];
            if (x == target)
                return true;
            (x &lt; target ? left : right) = mid;
        }
        return false;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var searchMatrix = function (matrix, target) {
    const m = matrix.length, n = matrix[0].length;
    let left = -1, right = m * n;
    while (left + 1 &lt; right) {
        let mid = Math.floor((left + right) / 2);
        let x = matrix[Math.floor(mid / n)][mid % n];
        if (x == target)
            return true;
        if (x &lt; target)
            left = mid;
        else
            right = mid;
    }
    return false;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsfind-first-and-last-position-of-element-in-sorted-arraydescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envType=study-plan-v2&amp;envId=top-100-liked">在排序数组中查找元素的第一个和最后一个位置</a></h2><p>二分查找返回数组里大于等于<code>target</code>的最小下标，这个下标对应元素的第一个位置。而最后一个位置是大于等于<code>target+1</code>的最小下标减一。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; searchRange(vector&lt;int&gt;&amp; nums, int target) {
        int n = nums.size();
        auto lower_bound = [&amp;](int k) {
            int left = -1, right = n;
            while (left + 1 &lt; right) {
                int mid = (left + right) / 2;
                (nums[mid] &lt; k ? left : right) = mid;
            }
            return right;
        };
        int i = lower_bound(target), j = lower_bound(target + 1);
        if (i == n || nums[i] != target)
            return {-1, -1};
        return {i, j - 1};
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function (nums, target) {
    let n = nums.length;
    function lowerBound(k) {
        let left = -1, right = n;
        while (left + 1 &lt; right) {
            let mid = Math.floor((left + right) / 2);
            if (nums[mid] &lt; k)
                left = mid;
            else
                right = mid;
        }
        return right;
    }
    let i = lowerBound(target), j = lowerBound(target + 1);
    if (i === n || nums[i] !== target)
        return [-1, -1];
    return [i, j - 1];
};
</code></pre>
<h2 id=""><a href="">搜索旋转排序数组</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id=""><a href="">寻找旋转排序数组中的最小值</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id=""><a href="">寻找两个正序数组的中位数</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">
</code></pre>
<h2 id="httpsleetcodecnproblemsvalid-parenthesesdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/valid-parentheses/description/?envType=study-plan-v2&amp;envId=top-100-liked">有效的括号</a></h2><p>如果是左括号就入栈，如果是右括号就判断是否和栈顶匹配，匹配就出栈，不匹配说明不是有效的括号。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool isValid(string s) {
        string t;
        for (auto ch : s) {
            if (ch == &#x27;(&#x27; || ch == &#x27;[&#x27; || ch == &#x27;{&#x27;)
                t += ch;
            else {
                if (t == &quot;&quot;)
                    return false;
                char x = t.back();
                if ((ch == &#x27;)&#x27; &amp;&amp; x == &#x27;(&#x27;) || (ch == &#x27;]&#x27; &amp;&amp; x == &#x27;[&#x27;) ||
                    (ch == &#x27;}&#x27; &amp;&amp; x == &#x27;{&#x27;))
                    t.pop_back();
                else
                    return false;
            }
        }
        return t == &quot;&quot;;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
    let t = &quot;&quot;;
    for (const ch of s) {
        if (ch === &#x27;(&#x27; || ch === &#x27;{&#x27; || ch === &#x27;[&#x27;)
            t += ch;
        else if (t.length === 0)
            return false;
        else {
            let x = t.at(-1);
            if ((ch == &#x27;)&#x27; &amp;&amp; x == &#x27;(&#x27;) || (ch === &#x27;]&#x27; &amp;&amp; x === &#x27;[&#x27;) || (ch === &#x27;}&#x27; &amp;&amp; x === &#x27;{&#x27;))
                t = t.slice(0, -1);
            else
                return false;
        }
    }
    return t.length === 0;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmin-stackdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/min-stack/description/?envType=study-plan-v2&amp;envId=top-100-liked">最小栈</a></h2><p>两个栈，一个保存添加的元素，一个维护每个前缀的最小值。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class MinStack {
public:
    vector&lt;int&gt; v, v_min;
    MinStack() {}

    void push(int val) {
        v.push_back(val);
        v_min.push_back((v_min.empty() || v_min.back() &gt; val) ? val
                                                              : v_min.back());
    }

    void pop() {
        v.pop_back();
        v_min.pop_back();
    }

    int top() { return v.back(); }

    int getMin() { return v_min.back(); }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj-&gt;push(val);
 * obj-&gt;pop();
 * int param_3 = obj-&gt;top();
 * int param_4 = obj-&gt;getMin();
 */
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">var MinStack = function () {
    this.stack = [];
    this.min_stack = [Infinity];
};

/** 
 * @param {number} val
 * @return {void}
 */
MinStack.prototype.push = function (val) {
    this.stack.push(val);
    this.min_stack.push(Math.min(this.min_stack.at(-1), val));
};

/**
 * @return {void}
 */
MinStack.prototype.pop = function () {
    this.stack.pop();
    this.min_stack.pop();

};

/**
 * @return {number}
 */
MinStack.prototype.top = function () {
    return this.stack.at(-1);
};

/**
 * @return {number}
 */
MinStack.prototype.getMin = function () {
    return this.min_stack.at(-1);
};

/** 
 * Your MinStack object will be instantiated and called as such:
 * var obj = new MinStack()
 * obj.push(val)
 * obj.pop()
 * var param_3 = obj.top()
 * var param_4 = obj.getMin()
 */
</code></pre>
<h2 id="httpsleetcodecnproblemsdecode-stringdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/decode-string/description/?envType=study-plan-v2&amp;envId=top-100-liked">字符串解码</a></h2><p>不是右括号就入栈，是右括号就一直出栈直至碰到左括号，弹出左括号后如果栈不为空，再出栈记录重复次数直至碰到非数字，将解码后的字符串重新入栈。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    string decodeString(string s) {
        string ans;
        for (auto ch : s) {
            if (ch == &#x27;]&#x27;) {
                string t;
                while (!ans.empty() &amp;&amp; ans.back() != &#x27;[&#x27;) {
                    t += ans.back();
                    ans.pop_back();
                }
                ans.pop_back();
                int k = 0, bit = 1;
                while (!ans.empty() &amp;&amp; isdigit(ans.back())) {
                    k += (ans.back() - &#x27;0&#x27;) * bit;
                    bit *= 10;
                    ans.pop_back();
                }
                ranges::reverse(t);
                for (int i = 0; i &lt; k; ++i)
                    ans += t;
            } else 
                ans += ch;
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {string}
 */
var decodeString = function (s) {
    let ans = &quot;&quot;;
    function isLetter(ch) {
        return /^[a-z]$/.test(ch);
    }
    function isNumber(ch) {
        return /^[0-9]$/.test(ch);
    }
    for (const ch of s) {
        if (ch != &#x27;]&#x27;)
            ans += ch;
        else {
            let t = &quot;&quot;, r = 0, bit = 0;
            while (ans.length) {
                let x = ans.at(-1);
                ans = ans.slice(0, -1);
                if (x === &#x27;[&#x27;) {
                    while (ans.length &amp;&amp; isNumber(ans.at(-1))) {
                        r = r + Number(ans.at(-1)) * (10 ** bit++);
                        ans = ans.slice(0, -1);
                    }
                    break;
                }
                t += x;
            }
            t = [...t].reverse().join(&quot;&quot;).repeat(r);
            ans += t;
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsdaily-temperaturesdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/daily-temperatures/description/?envType=study-plan-v2&amp;envId=top-100-liked">每日温度</a></h2><p>单调栈（单调递减）保存下标，如果当前温度大于栈顶温度，就出栈。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; dailyTemperatures(vector&lt;int&gt;&amp; temperatures) {
        vector&lt;int&gt; v, res(temperatures.size());
        for (int i = 0; i &lt; temperatures.size(); ++i) {
            while (v.size() &amp;&amp; temperatures[i] &gt; temperatures[v.back()]) {
                res[v.back()] = i - v.back();
                v.pop_back();
            }
            v.push_back(i);
        }
        return res;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var dailyTemperatures = function (temperatures) {
    let ans = new Array(temperatures.length).fill(0), tmp = [];
    for (let i = 0; i &lt; temperatures.length; ++i) {
        while (tmp.length &amp;&amp; temperatures[i] &gt; temperatures[tmp.at(-1)]) {
            let j = tmp.at(-1);
            ans[j] = i - j;
            tmp.pop();
        }
        tmp.push(i);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemslargest-rectangle-in-histogramdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/largest-rectangle-in-histogram/description/?envType=study-plan-v2&amp;envId=top-100-liked">柱状图中最大的矩形</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int largestRectangleArea(vector&lt;int&gt;&amp; heights) {
        int res = 0, n = (int)heights.size();
        vector&lt;int&gt; v, t, left(n), right(n);
        for (int i = 0; i &lt; n; ++i) {
            while (v.size() &amp;&amp; heights[i] &lt; heights[v.back()]) {
                right[v.back()] = i - v.back();
                v.pop_back();
            }
            v.push_back(i);
            while (t.size() &amp;&amp; heights[n - 1 - i] &lt; heights[t.back()]) {
                left[t.back()] = t.back() - n + 1 + i;
                t.pop_back();
            }
            t.push_back(n - 1 - i);
        }
        for (int i = 0; i &lt; n; ++i) {
            int width = right[i] == 0 ? n - i : right[i];
            width += left[i] == 0 ? i : left[i] - 1;
            res = max(res, width * heights[i]);
        }
        return res;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} heights
 * @return {number}
 */
var largestRectangleArea = function (heights) {
    heights.push(-1);
    let ans = 0, stack = [-1];
    for (let i = 0; i &lt; heights.length; ++i) {
        while (stack.length &gt; 1 &amp;&amp; heights[i] &lt;= heights[stack.at(-1)]) {
            let j = stack.pop();
            ans = Math.max(ans, (i - stack.at(-1) - 1) * heights[j]);
        }
        stack.push(i);
    }
    return ans;
};
</code></pre>
<h2 id="khttpsleetcodecnproblemskth-largest-element-in-an-arraydescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/kth-largest-element-in-an-array/description/?envType=study-plan-v2&amp;envId=top-100-liked">数组中的第K个最大元素</a></h2><p>最小堆，保留最大的<code>k</code>个数，根节点是堆中的最小元素，如果堆大小小于<code>k</code>，就直接加入元素，否则比较根节点和当前元素的大小，如果比当前元素小，就移出队列。</p><p>但是堆的插入/删除操作的时间复杂度是<code>O(logk)</code>吗，因此上述算法时间复杂度为<code>O(nlogk)</code>，要做到<code>O(n)</code>，用快排更好。</p><p>快速排序的思想就是每次随机选一个基准元素，然后进行分区操作，最后所有比基准元素小的元素都移到基准的左边，所有比基准元素大的元素都移到基准的右边，基准元素的位置也被确定。数组中的第<code>k</code>个最大元素对应排序后<code>nums[n-k]</code>，只要基准元素的下标为<code>n-k</code>，说明就找到了。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int findKthLargest(vector&lt;int&gt;&amp; nums, int k) {
        priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; q;
        for (auto num : nums) {
            if (q.size() &lt; k) {
                q.push(num);
            } else if (q.top() &lt; num) {
                q.pop();
                q.push(num);
            }
        }
        return q.top();
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function (nums, k) {
    const n = nums.length, targetIndex = n - k;
    let left = 0, right = n - 1;
    function partition(l, r) {
        const idx = l + Math.floor(Math.random() * (r - l + 1));
        const pivot = nums[idx];
        [nums[idx], nums[l]] = [nums[l], nums[idx]];
        let i = l + 1, j = r;
        while (1) {
            while (i &lt;= j &amp;&amp; nums[i] &lt; pivot)
                ++i;
            while (i &lt;= j &amp;&amp; nums[j] &gt; pivot)
                --j;
            if (i &gt;= j)
                break;
            [nums[i], nums[j]] = [nums[j], nums[i]];
            ++i;
            --j;
        }
        [nums[l], nums[j]] = [nums[j], nums[l]];
        return j;
    }
    while (1) {
        const i = partition(left, right);
        if (i === targetIndex)
            return nums[i];
        if (i &lt; targetIndex)
            left = i + 1;
        else
            right = i - 1;
    }
};
// var findKthLargest = function (nums, k) {
//     const heap = new MinPriorityQueue();
//     for (const num of nums) {
//         if (heap.size() &lt; k)
//             heap.enqueue(num);
//         else if (heap.front() &lt; num) {
//             heap.dequeue();
//             heap.enqueue(num);
//         }
//     }
//     return heap.front();
// };
</code></pre>
<h2 id="-k-httpsleetcodecnproblemstop-k-frequent-elementsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/top-k-frequent-elements/description/?envType=study-plan-v2&amp;envId=top-100-liked">前 K 个高频元素</a></h2><p>最小堆记录元素以及元素出现次数，出现次数优先。</p><p>用堆做的话比较简单，更优的做法是桶排序，把出现次数相同的元素放入一个桶中，倒序遍历桶更新答案。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; topKFrequent(vector&lt;int&gt;&amp; nums, int k) {
        vector&lt;int&gt; v;
        using pii = pair&lt;int, int&gt;;
        unordered_map&lt;int, int&gt; mp;
        for (auto num : nums)
            ++mp[num];
        priority_queue&lt;pii, vector&lt;pii&gt;, greater&lt;pii&gt;&gt; heap;
        for (auto [x, y] : mp) {
            if (heap.size() &lt; k)
                heap.push({y, x});
            else if (heap.top().first &lt; y) {
                heap.pop();
                heap.push({y, x});
            }
        }
        while (!heap.empty()) {
            v.push_back(heap.top().second);
            heap.pop();
        }
        return v;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var topKFrequent = function (nums, k) {
    const map = new Map();
    for (const num of nums)
        map.set(num, (map.get(num) ?? 0) + 1);
    const maxCnt = Math.max(...map.values())
    let buckets = new Array(maxCnt + 1).fill().map(() =&gt; []);
    for (const [key, val] of map)
        buckets[val].push(key);
    let ans = [];
    for (let i = maxCnt; ans.length &lt; k; --i)
        ans.push(...buckets[i]);
    return ans;
};
// var topKFrequent = function (nums, k) {
//     const map = new Map();
//     for (const num of nums)
//         map.set(num, (map.get(num) ?? 0) + 1);
//     const heap = new MinPriorityQueue({ compare: (a, b) =&gt; a.cnt - b.cnt });
//     for (const [key, val] of map) {
//         if (heap.size() &lt; k)
//             heap.enqueue({ cnt: val, num: key });
//         else if (heap.front().cnt &lt; val) {
//             heap.dequeue();
//             heap.enqueue({ cnt: val, num: key });
//         }
//     }
//     let ans = [];
//     while (!heap.isEmpty())
//         ans.push(heap.dequeue().num);
//     return ans;
// };
</code></pre>
<h2 id="httpsleetcodecnproblemsfind-median-from-data-streamdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/find-median-from-data-stream/description/?envType=study-plan-v2&amp;envId=top-100-liked">数据流的中位数</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class MedianFinder {
public:
    priority_queue&lt;int&gt; left;
    priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; right;
    MedianFinder() {}

    void addNum(int num) {
        int n = (int)left.size() + (int)right.size();
        if (right.empty())
            right.push(num);
        else {
            int rmin = right.top();
            if (n % 2) {
                if (rmin &lt; num) {
                    right.pop();
                    right.push(num);
                    left.push(rmin);
                } else
                    left.push(num);
                return;
            }
            int lmax = left.top();
            if (lmax &gt; num) {
                left.pop();
                right.push(lmax);
                left.push(num);
            } else {
                right.push(num);
            }
        }
    }

    double findMedian() {
        if (right.size() &gt; left.size())
            return right.top();
        return 1.0 * (left.top() + right.top()) / 2;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj-&gt;addNum(num);
 * double param_2 = obj-&gt;findMedian();
 */
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">class MedianFinder {
public:
    priority_queue&lt;int, vector&lt;int&gt;, less&lt;int&gt;&gt; left;
    priority_queue&lt;int, vector&lt;int&gt;, greater&lt;int&gt;&gt; right;

    MedianFinder() {}

    void addNum(int num) {
        int x = num;
        if (left.size() == right.size()) {
            if (right.size() &amp;&amp; right.top() &lt; num) {
                x = right.top();
                right.pop();
                right.push(num);
            }
            left.push(x);
        } else {
            if (left.size() &amp;&amp; left.top() &gt; num) {
                x = left.top();
                left.pop();
                left.push(num);
            }
            right.push(x);
        }
    }

    double findMedian() {
        if (left.size() == right.size())
            return (left.top() + right.top()) / 2.0;
        return left.top();
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj-&gt;addNum(num);
 * double param_2 = obj-&gt;findMedian();
 */
</code></pre>
<h2 id="httpsleetcodecnproblemsbest-time-to-buy-and-sell-stockdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/?envType=study-plan-v2&amp;envId=top-100-liked">买卖股票的最佳时机</a></h2><p>贪心，维护一个<code>minVal</code>为第<code>i</code>个元素之前的最小价格，我们要求的就是<code>nums[i]-minVal</code>的最大值。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int maxProfit(vector&lt;int&gt;&amp; prices) {
        int ans = 0, pre = prices[0];
        for (int i = 0; i &lt; prices.size(); ++i) {
            ans = max(ans, prices[i] - pre);
            pre = min(pre, prices[i]);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
    let ans = 0, minVal = prices[0];
    for (const price of prices) {
        minVal = Math.min(minVal, price);
        ans = Math.max(ans, price - minVal);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsjump-gamedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/jump-game/description/?envType=study-plan-v2&amp;envId=top-100-liked">跳跃游戏</a></h2><p>维护最远可到达位置。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool canJump(vector&lt;int&gt;&amp; nums) {
        for (int i = 0, j = 0; i &lt; nums.size(); ++i) {
            if(i &gt; j)
                return false;
            j = max(j, i + nums[i]);
        }
        return true;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canJump = function (nums) {
    for (let i = 0, j = 0; i &lt; nums.length; ++i) {
        if (i &gt; j)
            return false;
        j = Math.max(j, i + nums[i]);
    }
    return true;
};
</code></pre>
<h2 id="-iihttpsleetcodecnproblemsjump-game-iidescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/jump-game-ii/description/?envType=study-plan-v2&amp;envId=top-100-liked">跳跃游戏 II</a></h2><p>维护当前和下一次的最远可到达位置。当遍历到达当前边界时，说明必须进行一次跳跃，此时更新边界并增加步数。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int jump(vector&lt;int&gt;&amp; nums) {
        int ans = 0, cur = 0, nxt = 0;
        for (int i = 0; i + 1 &lt; nums.size(); ++i) {
            nxt = max(nxt, i + nums[i]);
            if(i == cur) {
                cur = nxt;
                ++ans;
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var jump = function (nums) {
    let ans = 0, cur = 0, nxt = 0;
    for (let i = 0; i + 1 &lt; nums.length; ++i) {
        nxt = Math.max(nxt, i + nums[i]);
        if (i == cur) {
            cur = nxt;
            ++ans;
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemspartition-labelsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/partition-labels/description/?envType=study-plan-v2&amp;envId=top-100-liked">划分字母区间</a></h2><p>维护每个字母出现的最远位置，初始化区间左右端点，更新右端点为区间内字母出现的最大下标，如果下标正好走到右端点时，说明区间合并完毕，加入答案并更新左端点。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;int&gt; partitionLabels(string s) {
        vector&lt;int&gt; cnt(26), ans;
        for (int i = 0; i &lt; s.size(); ++i)
            cnt[s[i] - &#x27;a&#x27;] = i;
        for (int i = 0, l = 0, r = 0; i &lt; s.size(); ++i) {
            r = max(r, cnt[s[i] - &#x27;a&#x27;]);
            if (i == r) {
                ans.push_back(r - l + 1);
                l = r + 1;
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {number[]}
 */
var partitionLabels = function (s) {
    var last = Array(26), ans = [];
    for (let i = 0; i &lt; s.length; ++i)
        last[s.charCodeAt(i) - &#x27;a&#x27;.charCodeAt(0)] = i;
    for (let i = 0, start = 0, end = 0; i &lt; s.length; ++i) {
        end = Math.max(end, last[s.charCodeAt(i) - &#x27;a&#x27;.charCodeAt(0)]);
        if (end === i) {
            ans.push(end - start + 1);
            start = i + 1;
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsclimbing-stairsenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/climbing-stairs/?envType=study-plan-v2&amp;envId=top-100-liked">爬楼梯</a></h2><p>状态转移方程为：</p><p>$$ f(i)=\left{
\begin{aligned}
&amp; f(i-1)+f(i-2), i &gt; 1\
&amp; i, i \leq 1
\end{aligned}
\right.
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int climbStairs(int n) {
        int f1 = 1, f2 = 1, f = 1;
        for (int i = 2; i &lt;= n; ++i) {
            f = f1 + f2;
            f1 = f2;
            f2 = f;
        }
        return f;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    let f1 = 1, f2 = 1, f = 1;
    for(let i = 2; i &lt;= n; ++i) {
        f = f1 + f2;
        f1 = f2;
        f2 = f;
    }
    return f;
};
</code></pre>
<h2 id="httpsleetcodecnproblemspascals-triangleenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/pascals-triangle/?envType=study-plan-v2&amp;envId=top-100-liked">杨辉三角</a></h2><p>杨辉三角每行第一个数和最后一个数是1，中间的数<code>nums[i][j] = nums[i-1][j-1] + nums[i-1][j]</code>。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    vector&lt;vector&lt;int&gt;&gt; generate(int numRows) {
        vector&lt;vector&lt;int&gt;&gt; ans{{1}};
        for (int r = 2; r &lt;= numRows; ++r) {
            vector&lt;int&gt; cur{1}, pre = ans.back();
            for(int i = 1; i &lt; pre.size(); ++i)
                cur.push_back(pre[i-1]+pre[i]);
            cur.push_back(1);
            ans.push_back(cur);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} numRows
 * @return {number[][]}
 */
var generate = function (numRows) {
    let ans = [[1]];
    for (let i = 1; i &lt; numRows; ++i) {
        let level = [1];
        for (let j = 1; j &lt; i; ++j)
            level.push(ans.at(-1)[j - 1] + ans.at(-1)[j]);
        level.push(1);
        ans.push(level);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemshouse-robberdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/house-robber/description/?envType=study-plan-v2&amp;envId=top-100-liked">打家劫舍</a></h2><p>状态转移方程为：</p><p>$$ f(i+1)=\left{
\begin{aligned}
&amp; max(f(i), f(i-1)+nums[i]), i &gt; 1\
&amp; 0, i = 0
\end{aligned}
\right.
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int rob(vector&lt;int&gt;&amp; nums) {
        int f = nums[0], f1 = 0, f2 = 0;
        for (int i = 0; i &lt; nums.size(); ++i) {
            f = max(f, f1 + nums[i]);
            f1 = f2;
            f2 = f;
        }
        return f;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function (nums) {
    let f = nums[0], f1 = 0, f2 = 0;
    for (let i = 0; i &lt; nums.length; ++i) {
        f = Math.max(f, f1 + nums[i]);
        f1 = f2;
        f2 = f;
    }
    return f;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsperfect-squaresdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/perfect-squares/description/?envType=study-plan-v2&amp;envId=top-100-liked">完全平方数</a></h2><p>状态转移方程为：</p><p>$$ f(i)=\left{
\begin{aligned}
&amp; min(f(i), f(i-j<em>j)+1), i \geq j</em>j\
&amp; 0, i = 0
\end{aligned}
\right.
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int numSquares(int n) {
        vector&lt;int&gt; dp(n + 1, 10001);
        dp[0] = 0;
        for (int i = 1; i &lt;= n; ++i) {
            for (int j = 0; j * j &lt;= i; ++j)
                dp[i] = min(dp[i], dp[i - j * j] + 1);
        }
        return dp[n];
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} n
 * @return {number}
 */
var numSquares = function (n) {
    var dp = Array(n + 1).fill(10001);
    dp[0] = 0;
    for (let i = 1; i &lt;= n; ++i) {
        for (let j = 0; j ** 2 &lt;= i; ++j)
            dp[i] = Math.min(dp[i], dp[i - j ** 2] + 1);
    }
    return dp[n];
};
</code></pre>
<h2 id="httpsleetcodecnproblemscoin-changedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/coin-change/description/?envType=study-plan-v2&amp;envId=top-100-liked">零钱兑换</a></h2><p><code>f(i)</code>表示凑成总金额<code>i</code>需要的最少硬币个数，状态转移方程为：</p><p>$$ f(i)=\left{
\begin{aligned}
&amp; min(f(i), f(i-coins[j])+1), i \geq coins[j]\
&amp; 0, i = 0
\end{aligned}
\right.
$$</p>
<pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int coinChange(vector&lt;int&gt;&amp; coins, int amount) {
        vector&lt;int&gt; dp(amount + 1, 10001);
        dp[0] = 0;
        for (int i = 1; i &lt;= amount; ++i) {
            for (auto coin : coins) {
                if (i &gt;= coin)
                    dp[i] = min(dp[i], dp[i - coin] + 1);
            }
        }
        return dp.back() &lt; 10001 ? dp.back() : -1;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function (coins, amount) {
    coins.sort((a, b) =&gt; a - b);
    let dp = new Array(amount + 1).fill(amount + 1);
    dp[0] = 0;
    for (let i = 0; i &lt;= amount; ++i) {
        for (let j = 0; j &lt; coins.length &amp;&amp; i &gt;= coins[j]; ++j)
            dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
    }
    return dp[amount] &lt; amount + 1 ? dp[amount] : -1;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsword-breakdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/word-break/description/?envType=study-plan-v2&amp;envId=top-100-liked">单词拆分</a></h2><p>哈希集合记录所有单词，如果当前子串<code>s(j-1,i)</code>在集合中并且前面的字符串也能够被拆分成若干单词即<code>dp[j-1]=1</code>，就使<code>dp[i]=1</code>。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool wordBreak(string s, vector&lt;string&gt;&amp; wordDict) {
        int max_len = 0, n = s.size();
        for (auto word : wordDict)
            max_len = max(max_len, (int)word.size());
        unordered_set&lt;string&gt; st(wordDict.begin(), wordDict.end());
        vector&lt;int&gt; dp(n + 1);
        dp[0] = 1;
        for (int i = 1; i &lt;= n; ++i) {
            for (int j = i - 1; j &gt;= max(i - max_len, 0); --j) {
                if (st.contains(s.substr(j, i - j)) &amp;&amp; dp[j]) {
                    dp[i] = 1;
                    break;
                }
            }
        }
        return dp[n];
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
var wordBreak = function (s, wordDict) {
    const set = new Set(wordDict);
    const dp = new Array(s.length + 1).fill(false);
    dp[0] = true;
    for (let i = 1; i &lt;= s.length; ++i) {
        for (let j = 1; j &lt;= i; ++j) {
            if (set.has(s.slice(j - 1, i)) &amp;&amp; dp[j - 1])
                dp[i] = true;
        }
    }
    return dp.at(-1);
};
</code></pre>
<h2 id="httpsleetcodecnproblemslongest-increasing-subsequenceenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/longest-increasing-subsequence/?envType=study-plan-v2&amp;envId=top-100-liked">最长递增子序列</a></h2><p><code>f(i)</code>表示以 <code>nums[i]</code> 结尾的最长递增子序列的长度，状态转移方程为：</p><p>$$ f(i)= max(f(i), f(j)+1), nums[i] &gt; nums[j]
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int lengthOfLIS(vector&lt;int&gt;&amp; nums) {
        vector&lt;int&gt; dp(nums.size() + 1, 1);
        int ans = 0;
        for (int i = 0; i &lt; nums.size(); ++i) {
            for (int j = 0; j &lt; i; ++j) {
                if (nums[i] &gt; nums[j])
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
    let n = nums.length, dp = new Array(n).fill(1);
    for (let i = 0; i &lt; n; ++i) {
        for (let j = 0; j &lt; i; ++j) {
            if (nums[i] &gt; nums[j])
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }
    return Math.max(...dp);
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmaximum-product-subarraydescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/maximum-product-subarray/description/?envType=study-plan-v2&amp;envId=top-100-liked">乘积最大子数组</a></h2><p>数组里面有负数，我们需要保存以<code>nums[i]</code>结尾的最大和最小的子数组乘积，状态转移方程为：</p><p>$$ 
f<em>{max}(i)= max(nums[i], nums[i]*f</em>{max}(i-1), nums[i]<em>f<em>{min}(i-1)) \
f</em>{min}(i)= min(nums[i], nums[i]</em>f<em>{max}(i-1), nums[i]*f</em>{min}(i-1))
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int maxProduct(vector&lt;int&gt;&amp; nums) {
        int ans = nums[0], f1 = 1, f2 = 1;
        for (auto num : nums) {
            int x = num * f1, y = num * f2;
            f1 = max({num, x, y});
            f2 = min({num, x, y});
            ans = max({ans, f1, f2});
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var maxProduct = function (nums) {
    let maxVal = 1, minVal = 1, ans = nums[0];
    for (const num of nums) {
        let x = num * maxVal, y = num * minVal;
        maxVal = Math.max(num, x, y);
        minVal = Math.min(num, x, y);
        ans = Math.max(ans, maxVal);
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemspartition-equal-subset-sumenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/partition-equal-subset-sum/?envType=study-plan-v2&amp;envId=top-100-liked">分割等和子集</a></h2><p>要分割成两个等和子集，那么每个子集的和一定是数组总和<code>sum</code>的一半，如果数组总和是奇数，那么一定不能等分两个子集。</p><p>问题目标转换成数组是否存在某个子集的和是<code>sum/2</code>，我们可以用动态规划，思考先遍历数组还是先遍历目标值？这个其实和<code>0-1</code>背包是一样的，我们知道应该先遍历物品再遍历容量，因为物品只能选一个（每次只处理一个物品，确保它只被考虑一次）。那么容量应该从小到大还是从大到小遍历呢？每次更新依赖的是上一轮的状态也就是物品还没被考虑时的情况，所以是从大到小。如果是从小到大，每次更新可能依赖本轮已更新的状态，就不兑了。状态转移方程为：</p><p>$$ f(i)=\left{
\begin{aligned}
&amp; f(i)\ ||\  f(i-nums[j]), i \geq nums[j]\
&amp; true, i = 0
\end{aligned}
\right.
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    bool canPartition(vector&lt;int&gt;&amp; nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0), target = sum / 2;
        if (sum % 2)
            return false;
        vector&lt;bool&gt; dp(target + 1);
        dp[0] = true;
        for (auto num : nums)
            for (int j = target; j &gt;= num; j--)
                dp[j] = dp[j] || dp[j - num];
        return dp[target];
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canPartition = function (nums) {
    let sum = nums.reduce((pre, val) =&gt; pre + val);
    if (sum % 2)
        return false;
    let target = Math.floor(sum / 2), dp = new Array(target + 1).fill(false);
    dp[0] = true;
    for (const num of nums) {
        for (let i = target; i &gt;= num; --i)
            dp[i] = dp[i] || dp[i - num];
    }
    return dp[target];
};
</code></pre>
<h2 id="httpsleetcodecnproblemslongest-valid-parenthesesdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/longest-valid-parentheses/description/?envType=study-plan-v2&amp;envId=top-100-liked">最长有效括号</a></h2><p>左括号下标入栈，每匹配一个右括号就出栈并更新答案，栈顶存放的是未被匹配的括号的下标。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int longestValidParentheses(string s) {
        vector&lt;int&gt; v{-1};
        int res = 0;
        for (int i = 0; i &lt; s.size(); ++i) {
            if (s[i] == &#x27;(&#x27;)
                v.push_back(i);
            else if (v.size() &gt; 1) {
                v.pop_back();
                res = max(res, i - v.back());
            } else
                v.back() = i;
        }
        return res;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {number}
 */
var longestValidParentheses = function (s) {
    let ans = 0, v = [-1];
    for (let i = 0; i &lt; s.length; ++i) {
        if (s[i] === &#x27;(&#x27;)
            v.push(i);
        else if (v.length &gt; 1) {
            v.pop();
            ans = Math.max(ans, i - v.at(-1));
        } else
            v[v.length - 1] = i;
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsunique-pathsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/unique-paths/description/?envType=study-plan-v2&amp;envId=top-100-liked">不同路径</a></h2><p>状态转移方程为：</p><p>$$ f(i,j)= f(i-1,j)+f(i,j-1) \
f(1,0) = f(0,1) = 1 
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int uniquePaths(int m, int n) {
        if (m == 1 || n == 1)
            return 1;
        vector&lt;vector&lt;int&gt;&gt; dp(m, vector&lt;int&gt;(n));
        dp[0][1] = dp[1][0] = 1;
        for (int i = 0; i &lt; m; ++i) {
            for (int j = 0; j &lt; n; ++j) {
                if (i &gt; 0)
                    dp[i][j] += dp[i - 1][j];
                if (j &gt; 0)
                    dp[i][j] += dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function (m, n) {
    if(m === 1 || n === 1)
        return 1;
    let dp = Array.from({ length: m }, () =&gt; new Array(n).fill(0));
    dp[0][1] = dp[1][0] = 1;
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (i &gt; 0)
                dp[i][j] += dp[i - 1][j];
            if (j &gt; 0)
                dp[i][j] += dp[i][j - 1];
        }
    }
    return dp[m - 1][n - 1];
};
</code></pre>
<h2 id="httpsleetcodecnproblemsminimum-path-sumdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/minimum-path-sum/description/?envType=study-plan-v2&amp;envId=top-100-liked">最小路径和</a></h2><p>状态转移方程为：</p><p>$$ f(i,j)=\left{
\begin{aligned}
&amp; min(f(i-1,j)+f(i,j-1)) + grid<span>[i][j]</span>,\  i &gt; 0, j &gt;0\
&amp; f(i-1,j) + grid<span>[i][j]</span>,\  i &gt; 0, j =0 \
&amp; f(i,j-1) + grid<span>[i][j]</span>,\  i = 0, j &gt;0 \
&amp; grid<span>[0][0]</span>, \ i = 0, j = 0
\end{aligned}
\right.
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int minPathSum(vector&lt;vector&lt;int&gt;&gt;&amp; grid) {
        int m = grid.size(), n = grid[0].size();
        vector&lt;vector&lt;int&gt;&gt; dp(m, vector&lt;int&gt;(n, INT_MAX));
        dp[0][0] = grid[0][0];
        for (int i = 0; i &lt; m; ++i) {
            for (int j = 0; j &lt; n; ++j) {
                if (i &amp;&amp; j)
                    dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
                else
                    dp[i][j] = (i ? dp[i - 1][j] : (j ? dp[i][j - 1] : 0)) +
                               grid[i][j];
            }
        }
        return dp[m - 1][n - 1];
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function (grid) {
    let m = grid.length, n = grid[0].length;
    let dp = Array.from({ length: m }, () =&gt; new Array(n).fill(Infinity));
    dp[0][0] = grid[0][0];
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (i &amp;&amp; j)
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            else
                dp[i][j] = (i ? dp[i - 1][j] : (j ? dp[i][j - 1] : 0)) +
                    grid[i][j];
        }
    }
    return dp[m - 1][n - 1];
};
</code></pre>
<h2 id="httpsleetcodecnproblemslongest-palindromic-substringsubmissions617516898envtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/longest-palindromic-substring/submissions/617516898/?envType=study-plan-v2&amp;envId=top-100-liked">最长回文子串</a></h2><p>中心扩展法，从中心向两边扩展，枚举<code>i</code>作为奇回文串的中心，枚举<code>(i-1,i)</code>作为偶回文串的中心。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    string str;
    pair&lt;int, int&gt; expandAroundCenter(int l, int r) {
        while (l &gt;= 0 &amp;&amp; r &lt; str.size() &amp;&amp; str[l] == str[r]) {
            --l;
            ++r;
        }
        return {l + 1, r - 1};
    }
    string longestPalindrome(string s) {
        str = s;
        int n = (int)s.size(), start = 0, end = 0;
        for (int i = 0; i &lt; n; ++i) {
            auto [l1, r1] = expandAroundCenter(i, i);
            auto [l2, r2] = expandAroundCenter(i - 1, i);
            if (r1 - l1 &gt; end - start) {
                start = l1;
                end = r1;
            }
            if (r2 - l2 &gt; end - start) {
                start = l2;
                end = r2;
            }
        }
        return s.substr(start, end - start + 1);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function (s) {
    function expandAroundCenter(l, r) {
        while (l &gt;= 0 &amp;&amp; r &lt; s.length &amp;&amp; s[l] === s[r]) {
            l--;
            r++;
        }
        return [l + 1, r - 1];
    }
    let start = 0, end = 0;
    for (let i = 0; i &lt; s.length; ++i) {
        let [l1, r1] = expandAroundCenter(i, i);
        let [l2, r2] = expandAroundCenter(i - 1, i);
        if (end - start &lt; r1 - l1) {
            start = l1;
            end = r1;
        }
        if (end - start &lt; r2 - l2) {
            start = l2;
            end = r2;
        }
    }
    return s.slice(start, end + 1);
};
</code></pre>
<h2 id="httpsleetcodecnproblemslongest-common-subsequencedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/longest-common-subsequence/description/?envType=study-plan-v2&amp;envId=top-100-liked">最长公共子序列</a></h2><p>状态转移方程为：</p><p>$$ f(i,j)=\left{
\begin{aligned}
&amp; f(i-1,j-1)+1,\  text1[i] = text2[j]\
&amp; max(f(i-1,j)+f(i,j-1)),\  text1[i] \neq text2[j]\
\end{aligned}
\right.
$$</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.size(), n = text2.size();
        vector&lt;vector&lt;int&gt;&gt; dp(m + 1, vector&lt;int&gt;(n + 1));
        for (int i = 0; i &lt; m; ++i) {
            for (int j = 0; j &lt; n; ++j) {
                if (text1[i] == text2[j])
                    dp[i + 1][j + 1] = 1 + dp[i][j];
                else
                    dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);
            }
        }
        return dp[m][n];
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} text1
 * @param {string} text2
 * @return {number}
 */
var longestCommonSubsequence = function (text1, text2) {
    let m = text1.length, n = text2.length;
    const dp = Array.from({ length: m + 1 }, () =&gt; new Array(n + 1).fill(0));
    for (let i = 0; i &lt; m; ++i) {
        for (let j = 0; j &lt; n; ++j) {
            if (text1[i] == text2[j])
                dp[i + 1][j + 1] = 1 + dp[i][j];
            else
                dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]);
        }
    }
    return dp[m][n];
};
</code></pre>
<h2 id="httpsleetcodecnproblemsedit-distancedescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/edit-distance/description/?envType=study-plan-v2&amp;envId=top-100-liked">编辑距离</a></h2><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector&lt;vector&lt;int&gt;&gt; cache(m + 1, vector&lt;int&gt;(n + 1, -1));
        auto dfs = [&amp;](auto dfs, int i, int j) {
            if (i &lt; 0 || j &lt; 0)
                return max(i + 1, j + 1);
            if (cache[i][j] == -1)
                cache[i][j] = dfs(dfs, i - 1, j - 1);
            if (word1[i] == word2[j])
                return cache[i][j];
            if (cache[i][j + 1] == -1)
                cache[i][j + 1] = dfs(dfs, i - 1, j);
            if (cache[i + 1][j] == -1)
                cache[i + 1][j] = dfs(dfs, i, j - 1);
            return min(cache[i][j], min(cache[i][j + 1], cache[i + 1][j])) + 1;
        };
        return dfs(dfs, m - 1, n - 1);
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {string} word1
 * @param {string} word2
 * @return {number}
 */
var minDistance = function (word1, word2) {
    let m = word1.length, n = word2.length;
    const dp = Array.from({ length: m + 1 }, () =&gt; new Array(n + 1).fill(0));
    for (let i = 0; i &lt; n; ++i)
        dp[0][i + 1] = i + 1;
    for (let i = 0; i &lt; m; ++i) {
        dp[i + 1][0] = i + 1;
        for (let j = 0; j &lt; n; ++j) {
            dp[i + 1][j + 1] = word1[i] === word2[j] ? dp[i][j] : Math.min(dp[i][j + 1], dp[i + 1][j], dp[i][j]) + 1;
        }
    }
    return dp[m][n];
};
</code></pre>
<h2 id="httpsleetcodecnproblemssingle-numberdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/single-number/description/?envType=study-plan-v2&amp;envId=top-100-liked">只出现一次的数字</a></h2><p>我们知道两个相同的数字异或的结果为<code>0</code>，那么只要遍历元素，用异或来消除所有出现两次的数字，剩下的一定是出现一次的元素。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int singleNumber(vector&lt;int&gt;&amp; nums) {
        int ans = 0;
        for(auto num: nums)
            ans ^= num;
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function (nums) {
    let ans = 0;
    for (const num of nums)
        ans ^= num;
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemsmajority-elementdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/majority-element/description/?envType=study-plan-v2&amp;envId=top-100-liked">多数元素</a></h2><p>存在严格众数，在数组中出现次数大于<code>⌊n/2⌋</code>，说明比其他元素出现次数加起来还要多，因此可以把众数出现次数与其他元素出现次数进行抵消。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int majorityElement(vector&lt;int&gt;&amp; nums) {
        int ans = 0, hp = 0;
        for (auto num : nums) {
            if (hp)
                hp += num == ans ? 1 : -1;
            else {
                ans = num;
                hp = 1;
            }
        }
        return ans;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function (nums) {
    let ans = 0, hp = 0;
    for (const num of nums) {
        if (hp)
            hp += num === ans ? 1 : -1;
        else {
            ans = num;
            hp = 1;
        }
    }
    return ans;
};
</code></pre>
<h2 id="httpsleetcodecnproblemssort-colorsdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/sort-colors/description/?envType=study-plan-v2&amp;envId=top-100-liked">颜色分类</a></h2><p>比较 tricky 的做法是维护数组中<code>0</code>和<code>1</code>的次数，分别记作<code>c0</code>和<code>c1</code>，那么只要把<code>nums[c1]</code>之前的数改成<code>1</code>，<code>nums[c0]</code>之前的数改成<code>0</code>，剩下的改成<code>2</code>就行。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    void sortColors(vector&lt;int&gt;&amp; nums) {
        int zero = 0, one = 0;
        for (auto num : nums) {
            zero += num == 0;
            one += num == 1;
        }
        for (int i = 0; i &lt; nums.size(); ++i) {
            if (i &lt; zero)
                nums[i] = 0;
            else if (i &lt; zero + one)
                nums[i] = 1;
            else
                nums[i] = 2;
        }
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var sortColors = function (nums) {
    let c0 = 0, c1 = 0;
    for (let i = 0; i &lt; nums.length; ++i) {
        const x = nums[i];
        nums[i] = 2;
        if(x &lt;= 1)
            nums[c1++] = 1;
        if(x === 0)
            nums[c0++] = 0;
    }
};
</code></pre>
<h2 id="httpsleetcodecnproblemsnext-permutationdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/next-permutation/description/?envType=study-plan-v2&amp;envId=top-100-liked">下一个排列</a></h2><p>找下一个排列，如果是单调递减的，那么下一个就是单调递增的排列。如果不是，那么就找比当前排列稍微大一点的。从后往前找，如果当前元素<code>nums[i]</code>大于前一个元素<code>nums[i-1]</code>，那么说明<code>nums[i]</code>开始一直到数组末尾的这一整段子序列是单调递减的，再从后往前找，找到第一个<code>nums[j]&gt;nums[i-1]</code>并交换两个数，再把<code>i</code>之后的数从小到大排序，这就是下一个排列。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    void nextPermutation(vector&lt;int&gt;&amp; nums) {
        int n = nums.size(), k = 0;
        for (int i = n - 1; i &gt; 0; --i) {
            if (nums[i] &gt; nums[i - 1]) {
                for (int j = n - 1; j &gt;= i; --j) {
                    if (nums[j] &gt; nums[i - 1]) {
                        swap(nums[j], nums[i - 1]);
                        break;
                    }
                }
                k = i;
                break;
            }
        }
        sort(nums.begin() + k, nums.end());
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var nextPermutation = function (nums) {
    let n = nums.length, k = 0;
    for (let i = n - 1; i &gt; 0; --i) {
        if (nums[i] &gt; nums[i - 1]) {
            for (let j = n - 1; j &gt;= i; --j) {
                if (nums[j] &gt; nums[i - 1]) {
                    [nums[j], nums[i - 1]] = [nums[i - 1], nums[j]];
                    break;
                }
            }
            k = i;
            break;
        }
    }
    nums = nums.splice(k, n - k, ...nums.slice(k).sort((a, b) =&gt; a - b));
};
</code></pre>
<h2 id="httpsleetcodecnproblemsfind-the-duplicate-numberdescriptionenvtypestudy-plan-v2envidtop-100-liked"><a href="https://leetcode.cn/problems/find-the-duplicate-number/description/?envType=study-plan-v2&amp;envId=top-100-liked">寻找重复数</a></h2><p>参考<a href="https://leetcode.cn/problems/find-the-duplicate-number/solutions/3797843/yong-ji-huan-shu-li-jie-zuo-fa-tong-142-tkoc2/?envType=study-plan-v2&amp;envId=top-100-liked">灵茶山艾府: 用基环树理解，做法同 142. 环形链表 II</a>，和“判断链表是否有环”的原理一样。如果你在位置i，下一站就去位置nums[i]，从0出发，你永远跳不出这个数组，而且永远不会回到位置 0，重复数就是环入口。</p><pre class="language-c++ lang-c++"><code class="language-c++ lang-c++">class Solution {
public:
    int findDuplicate(vector&lt;int&gt;&amp; nums) {
        int slow = 0, fast = 0;
        while(1) {
            slow = nums[slow];
            fast = nums[nums[fast]];
            if(slow == fast)
                break;
        }
        int head = 0;
        while(slow != head) {
            slow = nums[slow];
            head = nums[head];
        }
        return head;
    }
};
</code></pre>
<pre class="language-js lang-js"><code class="language-js lang-js">/**
 * @param {number[]} nums
 * @return {number}
 */
var findDuplicate = function (nums) {
    for (let i = 0; i &lt; nums.length; ++i) {
        while (nums[i] != i + 1) {
            let j = nums[i] - 1;
            if (nums[j] === nums[i])
                return nums[i];
            [nums[j], nums[i]] = [nums[i], nums[j]];
        }
    }
};
</code></pre></div><p style="text-align:right"><a href="https://sakulyn.top/posts/archive/lc-hot100#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/archive/lc-hot100</link><guid isPermaLink="true">https://sakulyn.top/posts/archive/lc-hot100</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Wed, 18 Feb 2026 09:01:10 GMT</pubDate></item><item><title><![CDATA[从零开始的饥荒生活：云服务器部署篇]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tinker/you-starve-i-starve">https://sakulyn.top/posts/tinker/you-starve-i-starve</a></blockquote><div><p>寒假里和三小伙伴一块玩饥荒来着，奈何受限于地理距离qwq，</p><h2 id="--">壹 · 前置知识</h2><p>如果你了解服务器的基础知识、饥荒服务器的运行原理，可以跳过这一章。</p><p>饥荒有两个世界：地上（主世界，Master）和地下（洞穴世界，Caves），运行程序后，每个世界都是一个独立的进程，它们之间通过本地网络（127.0.0.1）进行数据同步。</p><p>饥荒服务器使用UDP协议进行数据传输，UDP协议使用端口号来区别不同的服务，</p>
<h2 id="--">贰 · 开服准备</h2><p>为什么需要自建服务器？</p><p>24小时不间断运行，</p><p>2核心2G内存的服务器，3～4人联机</p>
<h2 id="--">弎 · 服务器配置</h2><h2 id="--">肆 · 饥荒，启动</h2></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tinker/you-starve-i-starve#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tinker/you-starve-i-starve</link><guid isPermaLink="true">https://sakulyn.top/posts/tinker/you-starve-i-starve</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Wed, 04 Feb 2026 05:12:16 GMT</pubDate></item><item><title><![CDATA[VTK.js：Web 端医学影像三维可视化的最佳实践]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tech/vtk-js">https://sakulyn.top/posts/tech/vtk-js</a></blockquote><div><h2 id="--">壹 · 前置知识</h2><h3 id="">什么是医学影像?</h3><h2 id="--">贰 · 三维可视化技术</h2><h2 id="--">叁 · 渲染管线</h2><h2 id="---demo">肆 · 最小 DEMO</h2><h2 id="">参考文献</h2></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tech/vtk-js#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tech/vtk-js</link><guid isPermaLink="true">https://sakulyn.top/posts/tech/vtk-js</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Thu, 15 Jan 2026 08:32:20 GMT</pubDate></item><item><title><![CDATA[Docker：OpenCamp 训练营 x CNB 平台 —— Learning Docker]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/tech/docker">https://sakulyn.top/posts/tech/docker</a></blockquote><div><h2 id="--">壹 · 从物理机到容器革命</h2><h3 id="">物理机时代</h3><p>在以前，要上线一款应用，需要找运维团队申请资源。如果没有资源，要先去采购一些服务器，然后上架到机架上去，插上网线，插上磁盘，通上电，装上操作系统。</p><p>那个时代运维团队和开发团队是有界限的，开发工程师是不能登到服务器上去操作的，因此需要与运维团队做约定，在服务器上安装需要的环境、依赖等等。</p><p>如果应用比较复杂，比如前端有 node，后端有 java，最好的方式是每台机器部署独立的应用，互不打扰。按这样部署完网站上线之后，如果网站的用户量很好，服务器灯打满了是最好的。</p><p>但是如果用户量并没有达到理想的状态，<strong>服务器的资源使用率就非常低</strong>，造成成本上的浪费。这个时候一个方法是持续推广应用，另一个方法是合并，也就是把前端服务、后端服务合并到一起，让多个服务共同跑在一台机器上，这样也能提高服务器资源使用率。但是这样可能出现的一个问题是<strong>不同服务的依赖库会有冲突</strong>，需要设计一个很复杂的策略来把服务迁移到同一个机子上去。</p><h3 id="">虚拟机时代</h3><p>在虚拟化技术出现之后，解决了物理机时代的一些痛点（硬件资源分配问题）。有了 KVM/XEN，我们可以<strong>把一台物理机资源层面切割成更小的力度，可以按需去索取服务器的配置（升配降配）</strong>。也是在这个时代，<strong>虚拟化催生了公有云</strong>。单台虚拟机通常用于测试，但是如果把多台物理机组合到一起，形成一个集群，就可以对用户提供一个购买资源的入口，这就是公有云。</p><p>这个阶段的一个典型架构是在一台机器上通过内核去做虚拟化，通常会有一个虚拟化层（VMware / KVM / XEN），<strong>在一台物理机上去虚出多个虚拟机，每个虚拟机就是一个独立的操作系统</strong>，有自己的内核，在内核之上再去叠加自己想要的依赖包，最终把应用部署起来。</p><p>在这个时候，DevOps 这个概念也流行起来了，从资源的开通、资源的配置到应用的配置，都可以通过代码的方式实现，可以调公有云的接口去把服务开通，然后调他们的一些功能特性，比如做一个虚拟机模板，把想要的操作系统打包进去，然后再去叠加一些带包的工具，批量部署你的应用，完全自动化掉。</p><h3 id="">容器时代</h3><p>为什么需要容器呢？—— 其实源自于 linux 的内核层面，有一些关键的点催生了docker，即 namespace、cgroup 和 unionFS。</p><p>虽然在虚拟化时代大家都在用 DevOps 去服务自己的业务，但是这个过程还可以再去加快一点，我们为什么要在一台机器上再去虚出每个虚拟机都去跑自己的内核呢？内核也是需要消耗资源的，而且虚拟机操作系统的镜像文件也很大，拉下来去部署这个效率是有优化空间的，如果遇到双11这种要去大规模做升降配，很多台慢，影响就比较大了。</p><p>因此有一些团队在探索一个事情：把虚拟机内核这层取消掉，<strong>在内核之上构建一个虚拟化的环境，让应用独享一个空间</strong>，也可以做资源隔离，互不打扰。</p><p>docker 不仅做了环境隔离这套机制，他还有一个更大的贡献是做了一个 hub，大家可以去分享做好的软件。这个时候的架构就变成了：<strong>硬件之上还是内核，但内核之上就不需要虚拟化引擎了，直接起一个容器引擎，容器引擎之上再去起容器，每个容器都是独立的，有自己的环境、依赖包，部署自己的 app</strong>。</p><h2 id="--docker-">贰 · docker 依赖的底层技术</h2><p>namespace、cgroups、unionFS 构成了容器的基石。docker 的核心并不是这三个，这三个技术是 linux 内核本身固有的东西，docker 只是把这三样东西做了一个封装，让用户去使用这三样东西的门槛降得很低。</p><p>在 docker 出来之前，一般需要手工调用linux内核的这些接口，创建一个 namespace，创建一个 cgroup 资源组，再去叠加一个 unionFS，最终才能启动一个容器。<strong>docker 是对这三个内核技术的封装</strong>。</p><h3 id="namespace">namespace</h3><p>在 linux 的内核里面可以创建一个子的命名空间，这个命名空间可以涵盖一些东西，比如用户系统、挂载点、用户名、进程 scope、网络管理、IPC，把这些东西每个都做成 namespace 之后，就可以起一个进程。这个进程虽然是跑在宿主机的内核之上，但是它是有自己的命名空间的。虽然宿主机可以同时管理到这两个 namespace，但是两个 namespace 之间是互相独立的，这个就为容器隔离奠定了基础。</p><p><strong>容器本身就是一个 linux 进程，只是被隔离的一个特殊的进程</strong>。</p><h3 id="cgroups">cgroups</h3><p>隔离了环境之后，还要隔离一个东西就是资源，要保证每个容器的资源不能超。如果一个容器资源超的很厉害的话，就会影响其他容器。所以必须有一个<strong>能够限制容器资源使用的一个东西—— cgroup</strong>，可以通过创建一个又一个的 cgroup 组，这个组里面可以设置一些策略，比如内存使用多少，cpu 使用多少，网络使用多少，IO 使用多少，然后把进程添加到组里面，这样就会受 cgroup 的管理了，不会再去跳出资源限制。</p><h3 id="unionfs">unionFS</h3><p>该以什么样的环境去跑这个进程呢？希望是把环境也做到进程里面去，携带程序运行的所有环境的一个进程，由此引出 rootfs，是一个操作系统所包含的文件、配置和目录，但是不包括操作系统内核。unionFS 是一个分层的文件系统，可以把多个物理目录挂载到同一个逻辑目录下，因此可以按层去做缓存。有了分层之后，就可以把发行版这一块做成一层，把语言/依赖包做成一层，这样如果有多个容器的话，同一层的东西只需要下载一次，对真正变化的东西再去做下载。</p><h2 id="--docker-">叁 · docker 基础</h2><p>查看 docker 版本</p><pre class="language-zsh lang-zsh"><code class="language-zsh lang-zsh">docker version
</code></pre>
<p>docker 是一个 CS 架构，即Client+Server。我们平时使用的 docker 客户端就相当于 client，他会有一个 docker 引擎（server），引擎会去帮我们做镜像构建、拉取镜像启动容器这些事情。</p><p>client和server是如何交互的？在linux里面两个进程交互的方式主要有：通过管道、共享内存、socket等，docker的CS交互用的是socket，客户端通过socket文件去跟docker的引擎做交互（<code>ls -a /var/run/docker.sock</code>）。</p>
<p>待整理、、、</p></div><p style="text-align:right"><a href="https://sakulyn.top/posts/tech/docker#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/tech/docker</link><guid isPermaLink="true">https://sakulyn.top/posts/tech/docker</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Thu, 20 Nov 2025 06:29:04 GMT</pubDate></item></channel></rss>