<?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>Wed, 13 May 2026 02:00:54 GMT</lastBuildDate><atom:link href="https://sakulyn.top/feed" rel="self" type="application/rss+xml"/><pubDate>Wed, 13 May 2026 02:00:54 GMT</pubDate><language><![CDATA[zh-CN]]></language><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><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 &amp;&amp; q) {
        p = p.next;
        q = q.next;
    }
    let a = headA, b = headB;
    while (p) {
        p = p.next;
        a = a.next;
    }
    while (q) {
        q = q.next;
        b = b.next;
    }
    while (a != b) {
        a = a.next;
        b = b.next;
    }
    return a;
};
</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[hello world]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://sakulyn.top/posts/archive/hello-world">https://sakulyn.top/posts/archive/hello-world</a></blockquote><span>你好，世界。</span><p style="text-align:right"><a href="https://sakulyn.top/posts/archive/hello-world#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://sakulyn.top/posts/archive/hello-world</link><guid isPermaLink="true">https://sakulyn.top/posts/archive/hello-world</guid><dc:creator><![CDATA[sakulyn]]></dc:creator><pubDate>Thu, 17 Jul 2025 08:21:37 GMT</pubDate></item></channel></rss>