<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>笑话人生</title>
  
  <subtitle>年华易逝 懂得珍惜</subtitle>
  <link href="https://www.cylong.com/atom.xml" rel="self"/>
  
  <link href="https://www.cylong.com/"/>
  <updated>2025-09-05T15:38:11.000Z</updated>
  <id>https://www.cylong.com/</id>
  
  <author>
    <name>cylong</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>动态规划求解单词拆分问题（LeetCode 139）</title>
    <link href="https://www.cylong.com/blog/2025/09/05/word-break/"/>
    <id>https://www.cylong.com/blog/2025/09/05/word-break/</id>
    <published>2025-09-05T15:38:11.000Z</published>
    <updated>2025-09-05T15:38:11.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你一个字符串 <code>s</code> 和一个字符串列表 <code>wordDict</code> 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 <code>s</code> 则返回 <code>true</code>。</p><p><strong>注意：</strong>不要求字典中出现的单词全部都使用，并且字典中的单词可以重复使用。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;leetcode&quot;</code>, <code>wordDict = [&quot;leet&quot;, &quot;code&quot;]</code><br><strong>输出：</strong><code>true</code><br><strong>解释：</strong>返回 <code>true</code> 因为 <code>leetcode</code> 可以由 <code>leet</code> 和 <code>code</code> 拼接成。</p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;applepenapple&quot;</code>, <code>wordDict = [&quot;apple&quot;, &quot;pen&quot;]</code><br><strong>输出：</strong><code>true</code><br><strong>解释：</strong>返回 <code>true</code> 因为 <code>applepenapple</code> 可以由 <code>apple</code> 、 <code>pen</code> 和 <code>apple</code> 拼接成。</p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;catsandog&quot;</code>, <code>wordDict = [&quot;cats&quot;, &quot;dog&quot;, &quot;sand&quot;, &quot;and&quot;, &quot;cat&quot;]</code><br><strong>输出：</strong><code>false</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= s.length &lt;= 300</code></li><li><code>1 &lt;= wordDict.length &lt;= 1000</code></li><li><code>1 &lt;= wordDict[i].length &lt;= 20</code></li><li><code>s</code> 和 <code>wordDict[i]</code> 仅由小写英文字母组成</li><li><code>wordDict</code> 中的所有字符串互不相同</li></ul><span id="more"></span><h1 id="动态规划"><a href="#动态规划" class="headerlink" title="动态规划"></a>动态规划</h1><p>我们可以使用动态规划来解决这个问题。定义 <code>dp[i]</code> 表示字符串 <code>s</code> 的前 <code>i</code> 个字符（即子串 <code>s[0:i-1]</code>）是否可以被拆分成字典中的单词。</p><h2 id="算法步骤"><a href="#算法步骤" class="headerlink" title="算法步骤"></a>算法步骤</h2><ol><li>将单词字典转换为哈希集合，提高查找效率</li><li>初始化 <code>dp[0] = true</code>，表示空字符串可以被拆分</li><li>对于每个位置 <code>i</code>（从 <code>1</code> 到字符串长度），检查所有可能的分割点 <code>j</code>（从 <code>0</code> 到 <code>i-1</code>）</li><li>如果 <code>dp[j]</code> 为真且子串 <code>s[j:i]</code> 在字典中，则设置 <code>dp[i] = true</code></li><li>最终 <code>dp[s.length()]</code> 就是整个字符串是否可以被拆分的答案</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">wordBreak</span><span class="params">(String s, List&lt;String&gt; wordDict)</span> &#123;</span><br><span class="line">    <span class="comment">// 将单词字典转换为哈希集合，提高查找效率</span></span><br><span class="line">    Set&lt;String&gt; wordDictSet = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;(wordDict);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 创建 dp 数组，dp[i] 表示 s 的前 i 个字符是否可以被拆分</span></span><br><span class="line">    <span class="type">boolean</span>[] dp = <span class="keyword">new</span> <span class="title class_">boolean</span>[s.length() + <span class="number">1</span>];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化：空字符串可以被拆分</span></span><br><span class="line">    dp[<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历字符串的每个位置</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= s.length(); i++) &#123;</span><br><span class="line">        <span class="comment">// 检查所有可能的分割点</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">            <span class="comment">// 如果前 j 个字符可以被拆分，且子串 s[j:i] 在字典中</span></span><br><span class="line">            <span class="keyword">if</span> (dp[j] &amp;&amp; wordDictSet.contains(s.substring(j, i))) &#123;</span><br><span class="line">                <span class="comment">// 设置 dp[i] 为 true，并跳出内层循环</span></span><br><span class="line">                dp[i] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 返回整个字符串是否可以被拆分</span></span><br><span class="line">    <span class="keyword">return</span> dp[s.length()];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="动态规划过程详解"><a href="#动态规划过程详解" class="headerlink" title="动态规划过程详解"></a>动态规划过程详解</h2><p>以 <code>s = &quot;leetcode&quot;</code>, <code>wordDict = [&quot;leet&quot;, &quot;code&quot;]</code> 为例，详细说明动态规划每一步的计算过程：</p><ol><li>初始化：<code>dp[0] = true</code>（空字符串可以被拆分）。</li><li>i=1：检查前 1 个字符 “l”。<ul><li>j=0：子串 “l” 不在字典中，<code>dp[1] = false</code>。</li></ul></li><li>i=2：检查前 2 个字符 “le”。<ul><li>j=0：子串 “le” 不在字典中。</li><li>j=1：子串 “e” 不在字典中，<code>dp[2] = false</code>。</li></ul></li><li>i=3：检查前 3 个字符 “lee”。<ul><li>j=0：子串 “lee” 不在字典中。</li><li>j=1：子串 “ee” 不在字典中。</li><li>j=2：子串 “e” 不在字典中，<code>dp[3] = false</code>。</li></ul></li><li>i=4：检查前 4 个字符 “leet”。<ul><li>j=0：子串 “leet” 在字典中，且 <code>dp[0]=true</code>，所以 <code>dp[4] = true</code>（跳出内层循环）。</li></ul></li><li>i=5：检查前 5 个字符 “leetc”。<ul><li>j=0：子串 “leetc” 不在字典中。</li><li>j=1：子串 “eetc” 不在字典中。</li><li>j=2：子串 “etc” 不在字典中。</li><li>j=3：子串 “tc” 不在字典中。</li><li>j=4：子串 “c” 不在字典中（注意：这里检查的是从索引 4 到 5 的子串 “c”，因为 <code>s.substring(4,5)</code> 返回 “c”），所以<code> dp[5] = false</code>。</li></ul></li><li>i=6：检查前 6 个字符 “leetco”。<ul><li>类似地，检查所有分割点，没有找到满足条件的子串，<code>dp[6] = false</code>。</li></ul></li><li>i=7：检查前 7 个字符 “leetcod”。<ul><li>没有满足条件的子串，<code>dp[7] = false</code>。</li></ul></li><li>i=8：检查前 8 个字符 “leetcode”。<ul><li>j=0：子串 “leetcode” 不在字典中。</li><li>j=1：子串 “eetcode” 不在字典中。</li><li>… 其他分割点也不满足。</li><li>j=4：子串 “code” 在字典中，且 <code>dp[4]=true</code>，所以 <code>dp[8] = true</code>。</li></ul></li><li>最终返回 <code>dp[8]=true</code>，表示 “leetcode” 可以被拆分为 “leet” 和 “code”。</li></ol><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n²)</code>，其中 <code>n</code> 是字符串的长度。因为有两层循环，外层循环遍历每个字符，内层循环最多遍历 <code>i</code> 次</li><li><strong>空间复杂度：</strong><code>O(n + m)</code>，其中 <code>n</code> 是字符串长度（<code>dp</code> 数组大小），<code>m</code> 是字典中的单词数量（哈希集合大小）</li></ul><h1 id="剪枝优化"><a href="#剪枝优化" class="headerlink" title="剪枝优化"></a>剪枝优化</h1><p>为了提升性能，我们引入两种剪枝优化：</p><ol><li><strong>最大长度剪枝：</strong><ul><li>通过计算字典中单词的最大长度，我们避免了检查那些长度超过最大单词长度的子串，这可以显著减少内层循环的次数。</li><li>例如，当检查位置i=5时，我们只需要从j=1开始检查，而不是从j=0开始。</li></ul></li><li><strong>从后向前检查：</strong><ul><li>通过从后向前检查分割点，我们优先检查较短的子串，这样一旦找到匹配的单词就可以提前退出循环，减少不必要的检查。</li><li>在实际应用中，较短的单词通常更常见，因此这种检查顺序可以提高效率。</li></ul></li></ol><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">wordBreak</span><span class="params">(String s, List&lt;String&gt; wordDict)</span> &#123;</span><br><span class="line">    Set&lt;String&gt; wordDictSet = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;(wordDict);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 计算字典中单词的最大长度</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">maxWordLength</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (String word : wordDict) &#123;</span><br><span class="line">        maxWordLength = Math.max(maxWordLength, word.length());</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">boolean</span>[] dp = <span class="keyword">new</span> <span class="title class_">boolean</span>[s.length() + <span class="number">1</span>];</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="literal">true</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= s.length(); i++) &#123;</span><br><span class="line">        <span class="comment">// 优化 1：只检查可能的分割点</span></span><br><span class="line">        <span class="comment">// 计算起始检查位置，避免检查长度超过最大单词长度的子串</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> Math.max(<span class="number">0</span>, i - maxWordLength);</span><br><span class="line">        <span class="comment">// 优化 2：从后向前检查，优先匹配较短的单词</span></span><br><span class="line">        <span class="comment">// 从 i-1 开始向前检查，一旦找到匹配的单词就可以提前退出循环</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> i - <span class="number">1</span>; j &gt;= start; j--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (dp[j] &amp;&amp; wordDictSet.contains(s.substring(j, i))) &#123;</span><br><span class="line">                dp[i] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> dp[s.length()];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>单词拆分问题是一个经典的动态规划问题，通过合理的剪枝优化，我们可以显著提高算法的性能。本文介绍了两种有效的优化技巧：长度剪枝和检查顺序优化，并通过代码示例展示了如何实现这些优化。在实际应用中，根据具体情况选择合适的优化策略，可以在保证正确性的同时提高算法的执行效率。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/word-break/description/" title="139. 单词拆分 | 力扣（LeetCode）">139. 单词拆分 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/word-break/solutions/302471/dan-ci-chai-fen-by-leetcode-solution/" title="139. 单词拆分 | 题解 | 力扣官方题解">139. 单词拆分 | 题解 | 力扣官方题解</a><br><a href="https://leetcode.cn/problems/word-break/solutions/2968135/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-chrs/" title="139. 单词拆分 | 题解 | 灵茶山艾府">139. 单词拆分 | 题解 | 灵茶山艾府</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你一个字符串 &lt;code&gt;s&lt;/code&gt; 和一个字符串列表 &lt;code&gt;wordDict&lt;/code&gt; 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 &lt;code&gt;s&lt;/code&gt; 则返回 &lt;code&gt;true&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;不要求字典中出现的单词全部都使用，并且字典中的单词可以重复使用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;leetcode&amp;quot;&lt;/code&gt;, &lt;code&gt;wordDict = [&amp;quot;leet&amp;quot;, &amp;quot;code&amp;quot;]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;true&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;返回 &lt;code&gt;true&lt;/code&gt; 因为 &lt;code&gt;leetcode&lt;/code&gt; 可以由 &lt;code&gt;leet&lt;/code&gt; 和 &lt;code&gt;code&lt;/code&gt; 拼接成。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;applepenapple&amp;quot;&lt;/code&gt;, &lt;code&gt;wordDict = [&amp;quot;apple&amp;quot;, &amp;quot;pen&amp;quot;]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;true&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;返回 &lt;code&gt;true&lt;/code&gt; 因为 &lt;code&gt;applepenapple&lt;/code&gt; 可以由 &lt;code&gt;apple&lt;/code&gt; 、 &lt;code&gt;pen&lt;/code&gt; 和 &lt;code&gt;apple&lt;/code&gt; 拼接成。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;catsandog&amp;quot;&lt;/code&gt;, &lt;code&gt;wordDict = [&amp;quot;cats&amp;quot;, &amp;quot;dog&amp;quot;, &amp;quot;sand&amp;quot;, &amp;quot;and&amp;quot;, &amp;quot;cat&amp;quot;]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;false&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= s.length &amp;lt;= 300&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= wordDict.length &amp;lt;= 1000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= wordDict[i].length &amp;lt;= 20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; 和 &lt;code&gt;wordDict[i]&lt;/code&gt; 仅由小写英文字母组成&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wordDict&lt;/code&gt; 中的所有字符串互不相同&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="动态规划" scheme="https://www.cylong.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="记忆化搜索" scheme="https://www.cylong.com/tags/%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2/"/>
    
  </entry>
  
  <entry>
    <title>单向链表重排：交替连接首尾节点（面试题）</title>
    <link href="https://www.cylong.com/blog/2025/09/02/linked-list-task/"/>
    <id>https://www.cylong.com/blog/2025/09/02/linked-list-task/</id>
    <published>2025-09-01T16:30:31.000Z</published>
    <updated>2025-09-01T16:30:31.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>假设你有一个单向链表 <code>L</code>，其首节点被标为 <code>head</code>，这个链表代表了小美的工作任务流程：</p><blockquote><p>L0 → L1 → … → Ln-1 → Ln</p></blockquote><p>你需要对其进行重新组织，以达到以下新的工作任务流程：</p><blockquote><p>L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …</p></blockquote><p>请注意，这里不能只修改节点任务的内容，而是需要实际地进行节点任务的交换。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>L = 1 → 2 → 3 → 4 → 5</code><br><strong>输出：</strong><code>1 → 5 → 2 → 4 → 3</code></p></blockquote><span id="more"></span><h1 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h1><p>为了解决这个问题，我们需要重新组织单向链表，使其满足特定的顺序：第一个节点后跟最后一个节点，然后第二个节点，接着倒数第二个节点，以此类推。关键在于实际交换节点，而不仅仅是修改节点值。</p><ol><li><strong>寻找链表中点：</strong>使用快慢指针技术找到链表的中间节点。慢指针每次移动一步，快指针每次移动两步。当快指针到达末尾时，慢指针指向链表的中间节点。</li><li><strong>分割链表：</strong>将链表从中间节点分割成两个部分。前半部分从头部到中间节点，后半部分从中间节点的下一个节点开始。</li><li><strong>反转后半部分：</strong>将后半部分链表反转，以便能够从末尾开始遍历。</li><li><strong>合并链表：</strong>将前半部分链表和反转后的后半部分链表交替合并，形成所需的顺序。</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 单向链表节点定义</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ListNode</span> &#123;</span><br><span class="line">    <span class="type">int</span> val;</span><br><span class="line">    ListNode next;</span><br><span class="line"></span><br><span class="line">    ListNode() &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ListNode(<span class="type">int</span> val) &#123;</span><br><span class="line">        <span class="built_in">this</span>.val = val;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ListNode(<span class="type">int</span> val, ListNode next) &#123;</span><br><span class="line">        <span class="built_in">this</span>.val = val;</span><br><span class="line">        <span class="built_in">this</span>.next = next;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 重新组织链表</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> head 链表头节点</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reorderList</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">        <span class="comment">// 处理空链表或只有一个节点的情况</span></span><br><span class="line">        <span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 步骤1: 使用快慢指针找到链表中点</span></span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">slow</span> <span class="operator">=</span> head;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">fast</span> <span class="operator">=</span> head.next;</span><br><span class="line">        <span class="keyword">while</span> (fast != <span class="literal">null</span> &amp;&amp; fast.next != <span class="literal">null</span>) &#123;</span><br><span class="line">            slow = slow.next;</span><br><span class="line">            fast = fast.next.next;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 步骤2: 分割链表为两部分</span></span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">second</span> <span class="operator">=</span> slow.next;</span><br><span class="line">        <span class="comment">// 断开链表</span></span><br><span class="line">        slow.next = <span class="literal">null</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 步骤3: 反转后半部分链表</span></span><br><span class="line">        second = reverseList(second);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 步骤4: 合并两个链表</span></span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">first</span> <span class="operator">=</span> head;</span><br><span class="line">        <span class="keyword">while</span> (first != <span class="literal">null</span> &amp;&amp; second != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">ListNode</span> <span class="variable">firstNext</span> <span class="operator">=</span> first.next;</span><br><span class="line">            <span class="type">ListNode</span> <span class="variable">secondNext</span> <span class="operator">=</span> second.next;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 将后半部分的节点插入到前半部分的节点之间</span></span><br><span class="line">            first.next = second;</span><br><span class="line">            second.next = firstNext;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 移动指针到下一位置</span></span><br><span class="line">            first = firstNext;</span><br><span class="line">            second = secondNext;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 反转链表</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> head 要反转的链表头节点</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 反转后的链表头节点</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> ListNode <span class="title function_">reverseList</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">curr</span> <span class="operator">=</span> head;</span><br><span class="line">        <span class="keyword">while</span> (curr != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">ListNode</span> <span class="variable">nextTemp</span> <span class="operator">=</span> curr.next;</span><br><span class="line">            curr.next = prev;</span><br><span class="line">            prev = curr;</span><br><span class="line">            curr = nextTemp;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> prev;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="算法示例"><a href="#算法示例" class="headerlink" title="算法示例"></a>算法示例</h2><ol><li>假设原始链表为：<code>1 → 2 → 3 → 4 → 5</code><ul><li>找到中点：节点 <code>3</code></li><li>分割链表：前半部分 <code>1 → 2 → 3</code>，后半部分 <code>4 → 5</code></li><li>反转后半部分：<code>5 → 4</code></li><li>合并链表：<code>1 → 5 → 2 → 4 → 3</code></li></ul></li><li>最终结果为：<code>1 → 5 → 2 → 4 → 3</code></li></ol><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>。寻找链表中点：<code>O(n/2) ≈ O(n)</code>，反转后半部分链表：<code>O(n/2) ≈ O(n)</code>，合并两个链表：<code>O(n/2) ≈ O(n)</code>，总体时间复杂度为线性 <code>O(n)</code>。</li><li><strong>空间复杂度：</strong><code>O(1)</code>。算法只使用了常数级别的额外空间（几个指针变量）。</li></ul><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;假设你有一个单向链表 &lt;code&gt;L&lt;/code&gt;，其首节点被标为 &lt;code&gt;head&lt;/code&gt;，这个链表代表了小美的工作任务流程：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;L0 → L1 → … → Ln-1 → Ln&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你需要对其进行重新组织，以达到以下新的工作任务流程：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;请注意，这里不能只修改节点任务的内容，而是需要实际地进行节点任务的交换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;L = 1 → 2 → 3 → 4 → 5&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;1 → 5 → 2 → 4 → 3&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="链表" scheme="https://www.cylong.com/tags/%E9%93%BE%E8%A1%A8/"/>
    
    <category term="双指针" scheme="https://www.cylong.com/tags/%E5%8F%8C%E6%8C%87%E9%92%88/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="回溯" scheme="https://www.cylong.com/tags/%E5%9B%9E%E6%BA%AF/"/>
    
    <category term="迭代" scheme="https://www.cylong.com/tags/%E8%BF%AD%E4%BB%A3/"/>
    
  </entry>
  
  <entry>
    <title>动态规划求解零钱兑换的最少硬币数量（LeetCode 322）</title>
    <link href="https://www.cylong.com/blog/2025/09/01/coin-change/"/>
    <id>https://www.cylong.com/blog/2025/09/01/coin-change/</id>
    <published>2025-09-01T15:46:44.000Z</published>
    <updated>2025-09-01T15:46:44.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你一个整数数组 <code>coins</code> ，表示不同面额的硬币；以及一个整数 <code>amount</code> ，表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额，返回 <code>-1</code> 。</p><p>你可以认为每种硬币的数量是无限的。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>coins = [1, 2, 5]</code>, <code>amount = 11</code><br><strong>输出：</strong><code>3</code><br><strong>解释：</strong><code>11 = 5 + 5 + 1</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>coins = [2]</code>, <code>amount = 3</code><br><strong>输出：</strong><code>-1</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>coins = [1]</code>, <code>amount = 0</code><br><strong>输出：</strong><code>0</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= coins.length &lt;= 12</code></li><li><code>1 &lt;= coins[i] &lt;= 2^31 - 1</code></li><li><code>0 &lt;= amount &lt;= 10^4</code></li></ul><span id="more"></span><h1 id="记忆化搜索（自顶向下）"><a href="#记忆化搜索（自顶向下）" class="headerlink" title="记忆化搜索（自顶向下）"></a>记忆化搜索（自顶向下）</h1><p>记忆化搜索（递归 + 记忆）使用递归来分解问题，并通过一个记忆数组存储已计算的子问题结果，避免重复计算。</p><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><ol><li><strong>基准情况：</strong><ul><li>如果金额 <code>amount</code> 为 <code>0</code>，返回 <code>0</code>。</li><li>如果金额为负，返回 <code>-1</code> 表示无解。</li></ul></li><li><strong>记忆化：</strong><ul><li>使用数组 <code>count</code> 存储子问题的解，其中 <code>count[i]</code> 表示金额 <code>i+1</code> 的最少硬币数（数组下标从 <code>0</code> 开始）。</li><li>如果当前金额已经计算过，直接返回记忆的结果。</li></ul></li><li><strong>递归计算：</strong><ul><li>遍历每个硬币，递归计算剩余金额 <code>rem - coin</code> 的解。</li><li>取所有有效解中的最小值加 <code>1</code>（当前硬币占一个数量）。</li></ul></li><li><strong>存储结果：</strong><ul><li>将当前金额的解存入记忆数组，并返回。</li></ul></li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">coinChange</span><span class="params">(<span class="type">int</span>[] coins, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (amount &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> coinChange(coins, amount, <span class="keyword">new</span> <span class="title class_">int</span>[amount]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">coinChange</span><span class="params">(<span class="type">int</span>[] coins, <span class="type">int</span> rem, <span class="type">int</span>[] count)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (rem &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (rem == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检查是否已经计算过：count[rem - 1] 对应金额 rem 的解</span></span><br><span class="line">    <span class="keyword">if</span> (count[rem - <span class="number">1</span>] != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> count[rem - <span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> <span class="variable">min</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> coin : coins) &#123;</span><br><span class="line">        <span class="comment">// 递归计算子问题</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">res</span> <span class="operator">=</span> coinChange(coins, rem - coin, count);</span><br><span class="line">        <span class="comment">// 如果子问题有解且更优，更新最小值</span></span><br><span class="line">        <span class="keyword">if</span> (res &gt;= <span class="number">0</span> &amp;&amp; res &lt; min) &#123;</span><br><span class="line">            min = <span class="number">1</span> + res;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 存储当前金额的解</span></span><br><span class="line">    count[rem - <span class="number">1</span>] = (min == Integer.MAX_VALUE) ? -<span class="number">1</span> : min;</span><br><span class="line">    <span class="keyword">return</span> count[rem - <span class="number">1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>动态运行过程（以 <code>coins = [1,2,5]</code>, <code>amount = 11</code> 为例）：</strong></p><ol><li>初始调用 <code>coinChange(coins, 11, new int[11])</code>，其中 <code>count</code> 数组大小为 <code>11</code>，初始化为 <code>0</code>。</li><li><code>rem = 11</code>，检查 <code>count[10]</code>（对应金额 <code>11</code>，但尚未计算），进入循环。</li><li>遍历硬币：<ul><li><code>coin = 1</code>：递归计算 <code>rem = 10</code>。<ul><li><code>rem = 10</code>，检查 <code>count[9]</code>（对应金额 <code>10</code>，未计算），进入循环。</li><li>类似地，递归计算 <code>rem = 9, 8, ..., 0</code>（深度优先）。</li><li>当 <code>rem = 0</code> 时，返回 <code>0</code>，然后回溯计算各金额的最小值。</li><li>例如，<code>rem = 1</code>：计算 <code>rem = 0</code> 返回 <code>0</code>，所以 <code>min = 1</code>，<code>count[0] = 1</code>。</li><li><code>rem = 2</code>：尝试硬币 <code>1</code>（<code>rem = 1</code> 返回 <code>1</code>），硬币 <code>2</code>（<code>rem = 0</code> 返回 <code>0</code>），所以 <code>min = min(1+1, 1+0) = 1</code>，<code>count[1] = 1</code>。</li><li><code>rem = 5</code>：尝试硬币<code>1</code>、<code>2</code>、<code>5</code>，最小值为 <code>1</code>（直接使用硬币 <code>5</code>），<code>count[4] = 1</code>。</li><li><code>rem = 10</code>：尝试硬币<code>1</code>、<code>2</code>、<code>5</code>。当硬币 <code>5</code> 时，<code>rem = 5</code> 返回 <code>1</code>，所以 <code>min = 1 + 1 = 2</code>。因此 <code>count[9] = 2</code>（金额 <code>10</code> 的解）。</li></ul></li><li><code>coin = 2</code>：递归计算 <code>rem = 9</code>，但可能已被计算过，直接使用 <code>count[8]</code>。</li><li><code>coin = 5</code>：递归计算 <code>rem = 6</code>，等。</li></ul></li><li>对于 <code>rem = 11</code>，当硬币 <code>1</code> 时，<code>rem = 10</code> 返回 <code>count[9] = 2</code>，所以 <code>min = 2 + 1 = 3</code>。</li><li>最终，<code>count[10]</code>（对应金额 <code>11</code>）被设置为 <code>3</code>。</li><li>过程中，每个金额最多计算一次，记忆数组避免了重复递归。</li></ol><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(amount * n)</code>，其中 <code>n</code> 是硬币种类数。每个金额计算一次，每次遍历所有硬币。</li><li><strong>空间复杂度：</strong><code>O(amount)</code>，用于记忆数组和递归调用栈。</li></ul><h1 id="动态规划（自底向上）"><a href="#动态规划（自底向上）" class="headerlink" title="动态规划（自底向上）"></a>动态规划（自底向上）</h1><p>动态规划使用一个 <code>dp</code> 数组来存储子问题的解，从最小金额开始逐步构建到目标金额。</p><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><ol><li><strong>初始化：</strong><ul><li>创建 <code>dp</code> 数组，<code>dp[i]</code> 表示金额 <code>i</code> 的最少硬币数。</li><li>设置 <code>dp[0] = 0</code>，其他初始化为 <code>Integer.MAX_VALUE</code>（表示尚未解决）。</li></ul></li><li>遍历硬币：<ul><li>对于每个硬币，从硬币面值开始遍历到总金额。</li><li>如果金额 <code>j</code> 减去硬币面值有解（即 <code>dp[j - coin] != MAX_VALUE</code>），则更新 <code>dp[j] = min(dp[j], dp[j - coin] + 1)</code>。</li></ul></li><li>返回结果：<ul><li>如果 <code>dp[amount]</code> 仍为 <code>MAX_VALUE</code>，返回 <code>-1</code>，否则返回 <code>dp[amount]</code>。</li></ul></li></ol><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">coinChange</span><span class="params">(<span class="type">int</span>[] coins, <span class="type">int</span> amount)</span> &#123;</span><br><span class="line">    <span class="comment">// 创建 dp 数组，dp[i] 表示金额 i 所需的最少硬币数</span></span><br><span class="line">    <span class="type">int</span>[] dp = <span class="keyword">new</span> <span class="title class_">int</span>[amount + <span class="number">1</span>];</span><br><span class="line">    <span class="comment">// 初始化 dp 数组，除 0 外都设为最大值</span></span><br><span class="line">    Arrays.fill(dp, Integer.MAX_VALUE);</span><br><span class="line">    dp[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 遍历每种硬币</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> coin : coins) &#123;</span><br><span class="line">        <span class="comment">// 对于当前硬币，从 coin 开始到 amount 更新 dp 数组</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> coin; j &lt;= amount; j++) &#123;</span><br><span class="line">            <span class="comment">// 如果 j-coin 有解，则更新 dp[j]</span></span><br><span class="line">            <span class="keyword">if</span> (dp[j - coin] != Integer.MAX_VALUE) &#123;</span><br><span class="line">                dp[j] = Math.min(dp[j], dp[j - coin] + <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果 dp[amount] 无解，返回-1</span></span><br><span class="line">    <span class="keyword">return</span> dp[amount] == Integer.MAX_VALUE ? -<span class="number">1</span> : dp[amount];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>动态运行过程（以 <code>coins = [1,2,5]</code>, <code>amount = 11</code> 为例）：</strong></p><ol><li>初始化：<code>dp = [0, MAX, MAX, ..., MAX]</code>（长度 <code>12</code> ）。</li><li>处理硬币 <code>1</code>：<ul><li><code>j</code> 从 <code>1</code> 到 <code>11</code>：<code>dp[1] = min(MAX, dp[0] + 1) = 1</code>，<code>dp[2] = min(MAX, dp[1] + 1) = 2</code>，…，<code>dp[11] = 11</code>。</li></ul></li><li>处理硬币 <code>2</code>：<ul><li><code>j</code> 从 <code>2</code> 到 <code>11</code>：</li><li><code>j=2</code>：<code>dp[2] = min(2, dp[0] + 1) = 1</code>。</li><li><code>j=3</code>：<code>dp[3] = min(3, dp[1] + 1) = 2</code>。</li><li><code>j=4</code>：<code>dp[4] = min(4, dp[2] + 1) = 2</code>。</li><li><code>j=5</code>：<code>dp[5] = min(5, dp[3] + 1) = 3</code>。</li><li>… 继续更新直到 <code>j=11</code>。</li></ul></li><li>处理硬币 <code>5</code>：<ul><li><code>j</code> 从 <code>5</code> 到 <code>11</code>：</li><li><code>j=5</code>：<code>dp[5] = min(3, dp[0] + 1) = 1</code>。</li><li><code>j=6</code>：<code>dp[6] = min(3, dp[1] + 1) = 2</code>。</li><li><code>j=7</code>：<code>dp[7] = min(3, dp[2] + 1) = 2</code>。</li><li><code>j=8</code>：<code>dp[8] = min(4, dp[3] + 1) = 3</code>。</li><li><code>j=9</code>：<code>dp[9] = min(4, dp[4] + 1) = 3</code>。</li><li><code>j=10</code>：<code>dp[10] = min(5, dp[5] + 1) = 2</code>。</li><li><code>j=11</code>：<code>dp[11] = min(6, dp[6] + 1) = 3</code>。</li></ul></li><li>最终 <code>dp[11] = 3</code>，返回 <code>3</code>。</li></ol><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(amount * n)</code>，因为外层循环遍历硬币，内层循环遍历金额。</li><li><strong>空间复杂度：</strong><code>O(amount)</code>，用于 <code>dp</code> 数组。</li></ul><h1 id="如何思考这道题"><a href="#如何思考这道题" class="headerlink" title="如何思考这道题"></a>如何思考这道题</h1><ol><li><strong>识别问题类型：</strong>这是一个优化问题（最小硬币数），且具有重叠子问题（同一金额可能多次计算）和最优子结构（当前金额的解依赖于子问题的解），适合用动态规划解决。</li><li><strong>定义状态：</strong>通常定义 <code>dp[i]</code> 为金额 <code>i</code> 所需的最少硬币数。</li><li><strong>状态转移方程：</strong>对于每个硬币 <code>c</code>，如果 <code>dp[i - c]</code> 计算过，则 <code>dp[i] = min(dp[i], dp[i - c] + 1)</code>。</li><li><strong>初始化：</strong><code>dp[0] = 0</code>，因为金额 <code>0</code> 不需要硬币；其他初始化为一个大值，表示未知。</li><li><strong>选择方法：</strong><ul><li><strong>记忆化搜索：</strong>适合递归思维，避免重复计算，但可能有递归开销。</li><li><strong>动态规划：</strong>直接迭代填充数组，通常更高效。</li></ul></li><li><strong>考虑边界：</strong>金额为 <code>0</code> 时返回 <code>0</code>；硬币面值可能大于金额，需跳过。</li><li><strong>测试用例：</strong>用示例验证代码，例如 <code>coins = [2]</code>, amount = <code>3</code> 应返回 <code>-1</code>。</li></ol><p>通过以上步骤，可以系统地解决此类动态规划问题。实际应用中，自底向上的动态规划往往更受欢迎，因为它避免了递归开销且代码简洁。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/coin-change/description/" title="322. 零钱兑换 | 力扣（LeetCode）">322. 零钱兑换 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/coin-change/solutions/132979/322-ling-qian-dui-huan-by-leetcode-solution/" title="322. 零钱兑换 | 题解 | 力扣官方题解">322. 零钱兑换 | 题解 | 力扣官方题解</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你一个整数数组 &lt;code&gt;coins&lt;/code&gt; ，表示不同面额的硬币；以及一个整数 &lt;code&gt;amount&lt;/code&gt; ，表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额，返回 &lt;code&gt;-1&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;你可以认为每种硬币的数量是无限的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;coins = [1, 2, 5]&lt;/code&gt;, &lt;code&gt;amount = 11&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;3&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;&lt;code&gt;11 = 5 + 5 + 1&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;coins = [2]&lt;/code&gt;, &lt;code&gt;amount = 3&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;-1&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;coins = [1]&lt;/code&gt;, &lt;code&gt;amount = 0&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= coins.length &amp;lt;= 12&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= coins[i] &amp;lt;= 2^31 - 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt;= amount &amp;lt;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="动态规划" scheme="https://www.cylong.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="记忆化搜索" scheme="https://www.cylong.com/tags/%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2/"/>
    
  </entry>
  
  <entry>
    <title>动态规划求解完全平方数的最小数量（LeetCode 279）</title>
    <link href="https://www.cylong.com/blog/2025/08/26/perfect-squares/"/>
    <id>https://www.cylong.com/blog/2025/08/26/perfect-squares/</id>
    <published>2025-08-26T15:23:03.000Z</published>
    <updated>2025-08-26T15:23:03.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你一个整数 <code>n</code> ，返回和为 <code>n</code> 的完全平方数的最少数量 。</p><p><strong>完全平方数</strong> 是一个整数，其值等于另一个整数的平方；换句话说，其值等于一个整数自乘的积。例如，<code>1</code>、<code>4</code>、<code>9</code> 和 <code>16</code> 都是完全平方数，而 <code>3</code> 和 <code>11</code> 不是。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>n = 12</code><br><strong>输出：</strong><code>3</code><br><strong>解释：</strong><code>12 = 4 + 4 + 4</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>n = 13</code><br><strong>输出：</strong><code>2</code><br><strong>解释：</strong><code>13 = 4 + 9</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= n &lt;= 10^4</code></li></ul><span id="more"></span><h1 id="动态规划"><a href="#动态规划" class="headerlink" title="动态规划"></a>动态规划</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>动态规划的思路是创建一个数组 <code>f</code>，其中 <code>f[i]</code> 表示组成数字 <code>i</code> 所需的最少完全平方数的数量。初始化时，<code>f[0] = 0</code>（因为 <code>0</code> 不需要任何平方数），然后对于每个 <code>i</code> 从 <code>1</code> 到 <code>n</code>，我们遍历所有可能的完全平方数 <code>j * j</code>（其中 <code>j</code> 从 <code>1</code> 开始，直到 <code>j * j &lt;= i</code>），并更新 <code>f[i]</code> 为 <code>f[i - j * j] + 1</code> 的最小值。</p><p>这种方法确保了对于每个数字 <code>i</code>，我们都能找到最优解。时间复杂度为 <code>O(n * sqrt(n))</code>，空间复杂度为 <code>O(n)</code>。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">numSquares</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="comment">// 创建动态规划数组，f[i] 表示组成 i 所需的最少完全平方数数量</span></span><br><span class="line">    <span class="type">int</span>[] f = <span class="keyword">new</span> <span class="title class_">int</span>[n + <span class="number">1</span>];</span><br><span class="line">    <span class="comment">// 从 1 到 n 计算每个数字的最少平方数数量</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">min</span> <span class="operator">=</span> Integer.MAX_VALUE; <span class="comment">// 初始化最小值</span></span><br><span class="line">        <span class="comment">// 遍历所有可能的完全平方数 j*j，其中 j*j &lt;= i</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j * j &lt;= i; j++) &#123;</span><br><span class="line">            <span class="comment">// 更新最小值，f[i - j*j] 表示减去一个平方数后的最优解</span></span><br><span class="line">            min = Math.min(min, f[i - j * j]);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 当前 i 的最优解为 min + 1（加上当前平方数）</span></span><br><span class="line">        f[i] = min + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> f[n]; <span class="comment">// 返回结果</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n * sqrt(n))</code>，因为对于每个数字 <code>i</code>，内层循环最多运行 <code>sqrt(i)</code> 次。</li><li><strong>空间复杂度：</strong><code>O(n)</code>，需要长度为 <code>n + 1</code> 的数组来存储状态。</li></ul><h1 id="数学方法（四平方和定理）"><a href="#数学方法（四平方和定理）" class="headerlink" title="数学方法（四平方和定理）"></a>数学方法（四平方和定理）</h1><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><p>四平方和定理指出，每个正整数都可以表示为最多四个完全平方数的和。利用这一定理，我们可以通过数学推导来减少计算：</p><ol><li>如果 <code>n</code> 本身是完全平方数，直接返回 <code>1</code>。</li><li>如果 <code>n</code> 满足公式 <code>n = 4^k * (8m + 7)</code>，那么它必须由四个完全平方数组成，返回 <code>4</code>。</li><li>检查 <code>n</code> 是否可以由两个完全平方数组成，即遍历 <code>i</code> 从 <code>1</code> 到 <code>sqrt(n)</code>，检查 <code>n - i * i</code> 是否为完全平方数。</li><li>如果以上都不满足，则返回 <code>3</code>。</li></ol><p>这种方法的时间复杂度为 <code>O(sqrt(n))</code>，空间复杂度为 <code>O(1)</code>，效率更高。</p><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">numSquares</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="comment">// 如果 n 是完全平方数，直接返回 1</span></span><br><span class="line">    <span class="keyword">if</span> (isPerfectSquare(n)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果 n 满足 4^k*(8m+7) 的形式，返回 4</span></span><br><span class="line">    <span class="keyword">if</span> (checkAnswer4(n)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">4</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检查是否可以由两个完全平方数组成</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i * i &lt;= n; i++) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> n - i * i;</span><br><span class="line">        <span class="comment">// 如果 j 是完全平方数，返回 2</span></span><br><span class="line">        <span class="keyword">if</span> (isPerfectSquare(j)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 否则返回 3</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">3</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断是否为完全平方数</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isPerfectSquare</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> (<span class="type">int</span>) Math.sqrt(x);</span><br><span class="line">    <span class="keyword">return</span> y * y == x;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断是否能表示为 4^k*(8m+7)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">checkAnswer4</span><span class="params">(<span class="type">int</span> x)</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (x % <span class="number">4</span> == <span class="number">0</span>) &#123;</span><br><span class="line">        x /= <span class="number">4</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> x % <span class="number">8</span> == <span class="number">7</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(sqrt(n))</code>，因为主要开销在检查两个平方数时循环最多 <code>sqrt(n)</code> 次。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，只使用了常数级别的额外空间。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>动态规划方法直观易懂，适用于一般范围的 <code>n</code>，但当 <code>n</code> 较大时可能效率较低。数学方法基于四平方和定理，效率更高，适合大数情况。在实际应用中，可以根据问题规模选择合适的方法。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/perfect-squares/description/" title="279. 完全平方数 | 力扣（LeetCode）">279. 完全平方数 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/perfect-squares/solutions/822940/wan-quan-ping-fang-shu-by-leetcode-solut-t99c/" title="279. 完全平方数 | 题解 | 力扣官方题解">279. 完全平方数 | 题解 | 力扣官方题解</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你一个整数 &lt;code&gt;n&lt;/code&gt; ，返回和为 &lt;code&gt;n&lt;/code&gt; 的完全平方数的最少数量 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完全平方数&lt;/strong&gt; 是一个整数，其值等于另一个整数的平方；换句话说，其值等于一个整数自乘的积。例如，&lt;code&gt;1&lt;/code&gt;、&lt;code&gt;4&lt;/code&gt;、&lt;code&gt;9&lt;/code&gt; 和 &lt;code&gt;16&lt;/code&gt; 都是完全平方数，而 &lt;code&gt;3&lt;/code&gt; 和 &lt;code&gt;11&lt;/code&gt; 不是。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;n = 12&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;3&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;&lt;code&gt;12 = 4 + 4 + 4&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;n = 13&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;2&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;&lt;code&gt;13 = 4 + 9&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= n &amp;lt;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="动态规划" scheme="https://www.cylong.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="数学" scheme="https://www.cylong.com/tags/%E6%95%B0%E5%AD%A6/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="记忆化搜索" scheme="https://www.cylong.com/tags/%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2/"/>
    
  </entry>
  
  <entry>
    <title>动态规划生成杨辉三角：层序构建与递推关系（LeetCode 118）</title>
    <link href="https://www.cylong.com/blog/2025/08/25/pascals-triangle/"/>
    <id>https://www.cylong.com/blog/2025/08/25/pascals-triangle/</id>
    <published>2025-08-25T15:18:40.000Z</published>
    <updated>2025-08-25T15:18:40.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给定一个非负整数 <code>numRows</code>，生成「杨辉三角」的前 <code>numRows</code> 行。在「杨辉三角」中，每个数是它左上方和右上方的数的和。</p><img src="/blog/2025/08/25/pascals-triangle/PascalTriangleAnimated2.gif" class="" title="杨辉三角"><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>numRows = 5</code><br><strong>输出：</strong><code>[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>numRows = 1</code><br><strong>输出：</strong><code>[[1]]</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= numRows &lt;= 30</code></li></ul><span id="more"></span><h1 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h1><ol><li><strong>初始化：</strong>创建一个结果列表 <code>res</code> 用于存储每一行的数据。</li><li><strong>逐行构建：</strong><ul><li>对于每一行 <code>i</code>（从 <code>0</code> 到 <code>numRows-1</code>），创建一个长度为 <code>i+1</code> 的数组。</li><li>将每行的首尾元素设置为 <code>1</code>。</li><li>对于中间的元素，每个元素等于上一行的同列和前一列元素之和。</li></ul></li><li><strong>添加行：</strong>将当前行转换为列表并添加到结果列表中。</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">generate</span><span class="params">(<span class="type">int</span> numRows)</span> &#123;</span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; numRows; i++) &#123;</span><br><span class="line">        Integer[] row = <span class="keyword">new</span> <span class="title class_">Integer</span>[i + <span class="number">1</span>];</span><br><span class="line">        row[<span class="number">0</span>] = <span class="number">1</span>; <span class="comment">// 每行第一个元素为1</span></span><br><span class="line">        row[i] = <span class="number">1</span>; <span class="comment">// 每行最后一个元素为1</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt; i; j++) &#123;</span><br><span class="line">            <span class="comment">// 当前元素等于上一行的前一个元素和当前元素之和</span></span><br><span class="line">            row[j] = res.get(i - <span class="number">1</span>).get(j - <span class="number">1</span>) + res.get(i - <span class="number">1</span>).get(j);</span><br><span class="line">        &#125;</span><br><span class="line">        res.add(Arrays.asList(row));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(numRows²)</code>。需要计算每个元素，总元素数量约为 <code>numRows²/2</code>。</li><li><strong>空间复杂度：</strong><code>O(numRows²)</code>。存储整个杨辉三角所需的空间。</li></ul><h1 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h1><h2 id="如何理解这个公式"><a href="#如何理解这个公式" class="headerlink" title="如何理解这个公式"></a>如何理解这个公式</h2><p><strong>递推式：</strong><code>c[i][j] = c[i−1][j−1] + c[i−1][j]</code></p><p>本质上是一个组合数恒等式，其中 <code>c[i][j]</code> 表示从 <code>i</code> 个不同物品中选出 <code>j</code> 个物品的方案数。如何理解上式呢？考虑其中某个物品选或不选：</p><ul><li><strong>选：</strong>问题变成从剩下 <code>i−1</code> 个不同物品中选出 <code>j−1</code> 个物品的方案数，即 <code>c[i−1][j−1]</code>。</li><li><strong>不选：</strong>问题变成从剩下 <code>i−1</code> 个不同物品中选出 <code>j</code> 个物品的方案数，即 <code>c[i−1][j]</code>。</li></ul><p>二者相加（加法原理），得：<code>c[i][j] = c[i−1][j−1] + c[i−1][j]</code></p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/pascals-triangle/description/" title="118. 杨辉三角 | 力扣（LeetCode）">118. 杨辉三角 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/pascals-triangle/solutions/2784222/jian-dan-ti-jian-dan-zuo-pythonjavaccgoj-z596/" title="118. 杨辉三角 | 题解 | 灵茶山艾府">118. 杨辉三角 | 题解 | 灵茶山艾府</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给定一个非负整数 &lt;code&gt;numRows&lt;/code&gt;，生成「杨辉三角」的前 &lt;code&gt;numRows&lt;/code&gt; 行。在「杨辉三角」中，每个数是它左上方和右上方的数的和。&lt;/p&gt;
&lt;img src=&quot;/blog/2025/08/25/pascals-triangle/PascalTriangleAnimated2.gif&quot; class=&quot;&quot; title=&quot;杨辉三角&quot;&gt;

&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;numRows = 5&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;numRows = 1&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[1]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= numRows &amp;lt;= 30&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="动态规划" scheme="https://www.cylong.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
    <category term="数学" scheme="https://www.cylong.com/tags/%E6%95%B0%E5%AD%A6/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>组合总和问题：回溯与剪枝策略应用（面试题）</title>
    <link href="https://www.cylong.com/blog/2025/08/24/combination-sum-m-n/"/>
    <id>https://www.cylong.com/blog/2025/08/24/combination-sum-m-n/</id>
    <published>2025-08-24T14:08:40.000Z</published>
    <updated>2025-08-24T14:08:40.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>提供一个整数数组 <code>nums</code>，从中选 <code>m</code> 个数，打印所有和为 <code>n</code> 的 二维数组，注意兼顾性能。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [-1, 1, 2, 3, 4, 5, 6]</code>, <code>m = 2</code>, <code>n = 5</code><br><strong>输出：</strong><code>[[1, 4], [2, 3], [-1, 6]]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [-1, 1, 2, 3, 4, 5, 6]</code>, <code>m = 3</code>, <code>n = 6</code><br><strong>输出：</strong><code>[[-1, 1, 6], [-1, 2, 5], [-1, 3, 4], [1, 2, 3]]</code></p></blockquote><span id="more"></span><h1 id="递归回溯-剪枝"><a href="#递归回溯-剪枝" class="headerlink" title="递归回溯 + 剪枝"></a>递归回溯 + 剪枝</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>我们需要从一个整数数组中找到所有长度为 <code>m</code> 且和为 <code>n</code> 的组合。为了兼顾性能，我们采用回溯法并结合剪枝策略来减少不必要的计算。具体步骤如下：</p><ol><li><strong>排序数组：</strong>首先对数组进行排序，这样可以方便地跳过重复元素，并利用有序性进行剪枝优化。</li><li><strong>预处理：</strong><ul><li><strong>前缀和数组：</strong>计算前缀和数组 <code>prefixSum</code>，<code>prefixSum[i]</code> 表示前 <code>i</code> 个元素的和，用于计算任意连续子数组的和。前缀和参考：<a href="/blog/2025/06/03/range-sum-query-immutable/" title="前缀和解法实现区间和查询（LeetCode 303） | 笑话人生">前缀和解法实现区间和查询（LeetCode 303） | 笑话人生</a></li><li><strong>后缀和数组：</strong>计算后缀和数组 <code>suffixSum</code>，<code>suffixSum[k]</code> 表示后 <code>k</code> 个元素的和，用于计算剩余元素的最大可能和。</li></ul></li><li><strong>回溯搜索：</strong>使用回溯方法生成所有可能的组合。在每一步中，检查当前组合的和是否可能达到目标值，通过以下剪枝条件提前终止不必要的分支：<ul><li>当前和加上剩余元素的最大可能和仍小于目标值。</li><li>当前和加上剩余元素的最小可能和已超过目标值。</li><li>当前元素加上当前和已超过目标值（由于数组已排序，后续元素只会更大）。</li><li>跳过重复元素，避免生成重复的组合。</li></ul></li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">combinationSum</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> m, <span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length &lt; m) &#123;</span><br><span class="line">        System.out.println(Arrays.toString(<span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>]));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 对数组进行排序</span></span><br><span class="line">    Arrays.sort(nums);</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="comment">// 计算前缀和数组，其中 prefixSum[i] 表示前 i 个数字的和</span></span><br><span class="line">    <span class="comment">// [0, -1, 0, 2, 5, 9, 14, 20]</span></span><br><span class="line">    <span class="type">int</span>[] prefixSum = <span class="keyword">new</span> <span class="title class_">int</span>[len + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; len; i++) &#123;</span><br><span class="line">        prefixSum[i + <span class="number">1</span>] = prefixSum[i] + nums[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 计算后缀和数组，其中 suffixSum[k] 表示最后 k 个数字的和</span></span><br><span class="line">    <span class="comment">// [0, 6, 11, 15, 18, 20, 21, 20]</span></span><br><span class="line">    <span class="type">int</span>[] suffixSum = <span class="keyword">new</span> <span class="title class_">int</span>[m + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">1</span>; k &lt;= m; k++) &#123;</span><br><span class="line">        suffixSum[k] = prefixSum[len] - prefixSum[len - k];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; results = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    List&lt;Integer&gt; current = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    backtrack(nums, m, n, <span class="number">0</span>, current, <span class="number">0</span>, results, prefixSum, suffixSum);</span><br><span class="line">    <span class="comment">// 打印结果</span></span><br><span class="line">    System.out.println(results);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> m, <span class="type">int</span> n, <span class="type">int</span> start, List&lt;Integer&gt; current, <span class="type">int</span> currentSum,</span></span><br><span class="line"><span class="params">                       List&lt;List&lt;Integer&gt;&gt; results, <span class="type">int</span>[] prefixSum, <span class="type">int</span>[] suffixSum)</span> &#123;</span><br><span class="line">    <span class="comment">// 还需要选择的数字个数</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">r</span> <span class="operator">=</span> m - current.size();</span><br><span class="line">    <span class="keyword">if</span> (r == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (currentSum == n) &#123;</span><br><span class="line">            results.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(current));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检查剩余数字是否足够</span></span><br><span class="line">    <span class="keyword">if</span> (start + r &gt; nums.length) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 计算从 start 开始 r 个数字的最小和</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">minSumFromStart</span> <span class="operator">=</span> prefixSum[start + r] - prefixSum[start];</span><br><span class="line">    <span class="comment">// 如果当前和加上剩余元素的最小可能和已超过目标值，直接返回。</span></span><br><span class="line">    <span class="keyword">if</span> (currentSum + minSumFromStart &gt; n) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果当前和加上剩余元素的最大可能和仍小于目标值，直接返回。</span></span><br><span class="line">    <span class="keyword">if</span> (currentSum + suffixSum[r] &lt; n) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> start; i &lt; nums.length; i++) &#123;</span><br><span class="line">        <span class="comment">// 跳过重复元素，避免重复组合</span></span><br><span class="line">        <span class="keyword">if</span> (i &gt; start &amp;&amp; nums[i] == nums[i - <span class="number">1</span>]) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 因为数组已排序，如果当前数字加上当前和已经大于n，后面的数字更大，直接跳出循环</span></span><br><span class="line">        <span class="keyword">if</span> (currentSum + nums[i] &gt; n) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        current.add(nums[i]);</span><br><span class="line">        backtrack(nums, m, n, i + <span class="number">1</span>, current, currentSum + nums[i], results, prefixSum, suffixSum);</span><br><span class="line">        <span class="comment">// 回溯</span></span><br><span class="line">        current.remove(current.size() - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong>排序数组的时间复杂度为 <code>O(l log l)</code> 其中 <code>l</code> 是数组长度。回溯的时间复杂度最坏情况下为 <code>O(C(l, m))</code>，即组合数，但通过剪枝策略，实际运行时间会大大减少。</li><li><strong>空间复杂度：</strong><code>O(m)</code> 用于递归调用栈和存储当前组合，<code>O(l)</code> 用于前缀和数组，<code>O(m)</code> 用于后缀和数组。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>通过排序数组、计算前缀和与后缀和，并结合回溯与剪枝策略，我们能够高效地解决组合求和问题。这种方法不仅确保了正确性，还通过剪枝显著提高了性能，适用于处理较大的输入数组。</p><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;提供一个整数数组 &lt;code&gt;nums&lt;/code&gt;，从中选 &lt;code&gt;m&lt;/code&gt; 个数，打印所有和为 &lt;code&gt;n&lt;/code&gt; 的 二维数组，注意兼顾性能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [-1, 1, 2, 3, 4, 5, 6]&lt;/code&gt;, &lt;code&gt;m = 2&lt;/code&gt;, &lt;code&gt;n = 5&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[1, 4], [2, 3], [-1, 6]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [-1, 1, 2, 3, 4, 5, 6]&lt;/code&gt;, &lt;code&gt;m = 3&lt;/code&gt;, &lt;code&gt;n = 6&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[-1, 1, 6], [-1, 2, 5], [-1, 3, 4], [1, 2, 3]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="深度优先搜索" scheme="https://www.cylong.com/tags/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
    
    <category term="剪枝" scheme="https://www.cylong.com/tags/%E5%89%AA%E6%9E%9D/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="前缀和" scheme="https://www.cylong.com/tags/%E5%89%8D%E7%BC%80%E5%92%8C/"/>
    
    <category term="回溯" scheme="https://www.cylong.com/tags/%E5%9B%9E%E6%BA%AF/"/>
    
    <category term="后缀和" scheme="https://www.cylong.com/tags/%E5%90%8E%E7%BC%80%E5%92%8C/"/>
    
    <category term="面试" scheme="https://www.cylong.com/tags/%E9%9D%A2%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>贪心算法求解最小跳跃次数（LeetCode 45）</title>
    <link href="https://www.cylong.com/blog/2025/08/23/jump-game-ii/"/>
    <id>https://www.cylong.com/blog/2025/08/23/jump-game-ii/</id>
    <published>2025-08-22T16:14:39.000Z</published>
    <updated>2025-08-22T16:14:39.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给定一个长度为 <code>n</code> 的 <strong>0 索引</strong>整数数组 <code>nums</code>。初始位置为 <code>nums[0]</code>。每个元素 <code>nums[i]</code> 表示从索引 <code>i</code> 向后跳转的最大长度。换句话说，如果你在索引 <code>i</code> 处，你可以跳转到任意 <code>(i + j)</code> 处（<code>0 &lt;= j &lt;= nums[i]</code> 且 <code>i + j &lt; n</code>）</p><p>返回到达 <code>n - 1</code> 的最小跳跃次数。测试用例保证可以到达 <code>n - 1</code>。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [2, 3, 1, 1, 4]</code><br><strong>输出：</strong><code>2</code><br><strong>解释：</strong>跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置，跳 1 步，然后跳 3 步到达数组的最后一个位置。</p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [2, 3, 0, 1, 4]</code><br><strong>输出：</strong><code>2</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= nums.length &lt;= 10^4</code></li><li><code>0 &lt;= nums[i] &lt;= 1000</code></li><li>题目保证可以到达 <code>n - 1</code></li></ul><span id="more"></span><h1 id="贪心算法"><a href="#贪心算法" class="headerlink" title="贪心算法"></a>贪心算法</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><ol><li>初始化三个变量：<ul><li><code>max：</code>记录当前能够到达的最远位置</li><li><code>end：</code>记录当前跳跃能够到达的边界，到达边界时，步数需要加 1，并且更新边界为当前能够到达的最远位置 max</li><li><code>step：</code>记录跳跃的步数</li></ul></li><li>遍历数组：<ul><li>更新当前能够到达的最远位置</li><li>当遍历到当前边界时，意味着这一步已经无法再继续前进，须进行一次跳跃，更新边界为当前能够到达的最远位置，并增加跳跃次数</li><li>注意不需要遍历最后一个元素，因为当到达最后一个元素时不需要再跳跃</li></ul></li><li>返回跳跃次数</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">jump</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="comment">// 处理边界情况：数组为空或长度为0</span></span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> <span class="number">0</span>;    <span class="comment">// 记录当前能够到达的最远位置</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">end</span> <span class="operator">=</span> <span class="number">0</span>;    <span class="comment">// 记录当前跳跃能够到达的边界</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">step</span> <span class="operator">=</span> <span class="number">0</span>;   <span class="comment">// 记录跳跃次数</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历数组，注意不需要遍历最后一个元素</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length - <span class="number">1</span>; i++) &#123;</span><br><span class="line">        <span class="comment">// 更新当前能够到达的最远位置</span></span><br><span class="line">        <span class="keyword">if</span> (i + nums[i] &gt; max) &#123;</span><br><span class="line">            max = i + nums[i];</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 当遍历到当前边界时，必须进行一次跳跃</span></span><br><span class="line">        <span class="keyword">if</span> (i == end) &#123;</span><br><span class="line">            step++;     <span class="comment">// 增加跳跃次数</span></span><br><span class="line">            end = max;  <span class="comment">// 更新边界为当前能够到达的最远位置</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> step;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，其中 <code>n</code> 是数组的长度。我们只需要遍历数组一次。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，只使用了常数级别的额外空间。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>本题使用贪心算法，通过维护当前能够到达的最远位置和当前跳跃的边界，确保了每次跳跃都是最优选择，从而得到最少的跳跃次数。算法的时间复杂度为线性，空间复杂度为常数，是非常高效的解决方案。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/jump-game-ii/description/" title="45. 跳跃游戏 II | 力扣（LeetCode）">45. 跳跃游戏 II | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/jump-game-ii/solutions/2926993/tu-jie-yi-zhang-tu-miao-dong-tiao-yue-yo-h2d4/" title="45. 跳跃游戏 II | 题解 | 灵茶山艾府">45. 跳跃游戏 II | 题解 | 灵茶山艾府</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给定一个长度为 &lt;code&gt;n&lt;/code&gt; 的 &lt;strong&gt;0 索引&lt;/strong&gt;整数数组 &lt;code&gt;nums&lt;/code&gt;。初始位置为 &lt;code&gt;nums[0]&lt;/code&gt;。每个元素 &lt;code&gt;nums[i]&lt;/code&gt; 表示从索引 &lt;code&gt;i&lt;/code&gt; 向后跳转的最大长度。换句话说，如果你在索引 &lt;code&gt;i&lt;/code&gt; 处，你可以跳转到任意 &lt;code&gt;(i + j)&lt;/code&gt; 处（&lt;code&gt;0 &amp;lt;= j &amp;lt;= nums[i]&lt;/code&gt; 且 &lt;code&gt;i + j &amp;lt; n&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;返回到达 &lt;code&gt;n - 1&lt;/code&gt; 的最小跳跃次数。测试用例保证可以到达 &lt;code&gt;n - 1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [2, 3, 1, 1, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;2&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置，跳 1 步，然后跳 3 步到达数组的最后一个位置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [2, 3, 0, 1, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;2&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= nums.length &amp;lt;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt;= nums[i] &amp;lt;= 1000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;题目保证可以到达 &lt;code&gt;n - 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="贪心算法" scheme="https://www.cylong.com/tags/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>贪心算法判断数组终点可达性：维护最大位置（LeetCode 55）</title>
    <link href="https://www.cylong.com/blog/2025/08/22/jump-game/"/>
    <id>https://www.cylong.com/blog/2025/08/22/jump-game/</id>
    <published>2025-08-21T16:49:13.000Z</published>
    <updated>2025-08-21T16:49:13.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你一个非负整数数组 <code>nums</code> ，你最初位于数组的第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。</p><p>判断你是否能够到达最后一个下标，如果可以，返回 <code>true</code> ；否则，返回 <code>false</code> 。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [2, 3, 1, 1, 4]</code><br><strong>输出：</strong><code>true</code><br><strong>解释：</strong>可以先跳 1 步，从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。</p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [3, 2, 1, 0, 4]</code><br><strong>输出：</strong><code>false</code><br><strong>解释：</strong>无论怎样，总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 ， 所以永远不可能到达最后一个下标。</p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= nums.length &lt;= 10^4</code></li><li><code>0 &lt;= nums[i] &lt;= 10^5</code></li></ul><span id="more"></span><h1 id="贪心算法"><a href="#贪心算法" class="headerlink" title="贪心算法"></a>贪心算法</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>维护一个变量 <code>max</code>，表示当前能够到达的最远位置。遍历数组时，对于每个位置 <code>i</code>，如果 <code>i</code> 在当前能够到达的范围内（即 <code>i &lt;= max</code>），则更新 <code>max</code> 为 <code>max</code> 和 <code>i + nums[i]</code> 中的较大值。如果 <code>max</code> 已经大于或等于数组的最后一个位置（即数组长度减一），则说明可以到达最后一个位置，返回 <code>true</code>。如果遍历结束都没有达到最后一个位置，则返回 <code>false</code>。</p><p>这种方法确保了在每一步都贪心地扩展最远可达距离，从而高效地判断是否可达。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">canJump</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> nums.length; <span class="comment">// 数组长度</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 当前能到达的最远位置</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; len; i++) &#123;</span><br><span class="line">        <span class="comment">// 如果当前索引i在可达范围内，才更新最远位置</span></span><br><span class="line">        <span class="keyword">if</span> (i &lt;= max) &#123;</span><br><span class="line">            <span class="comment">// 更新最远可达位置：取当前最远位置和i + nums[i]的较大值</span></span><br><span class="line">            max = Math.max(max, i + nums[i]);</span><br><span class="line">            <span class="comment">// 如果最远可达位置已经超过或等于最后一个位置，则返回true</span></span><br><span class="line">            <span class="keyword">if</span> (max &gt;= len - <span class="number">1</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 遍历结束仍未到达最后位置，返回false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，其中 <code>n</code> 是数组的长度。我们只需要遍历数组一次。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，只使用了常数级别的额外空间。</li></ul><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/jump-game/description/" title="55. 跳跃游戏 | 力扣（LeetCode）">55. 跳跃游戏 | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你一个非负整数数组 &lt;code&gt;nums&lt;/code&gt; ，你最初位于数组的第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。&lt;/p&gt;
&lt;p&gt;判断你是否能够到达最后一个下标，如果可以，返回 &lt;code&gt;true&lt;/code&gt; ；否则，返回 &lt;code&gt;false&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [2, 3, 1, 1, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;true&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;可以先跳 1 步，从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [3, 2, 1, 0, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;false&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;无论怎样，总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 ， 所以永远不可能到达最后一个下标。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= nums.length &amp;lt;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt;= nums[i] &amp;lt;= 10^5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="贪心算法" scheme="https://www.cylong.com/tags/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>一次遍历计算买卖股票的最佳时机（LeetCode 121）</title>
    <link href="https://www.cylong.com/blog/2025/08/21/best-time-to-buy-and-sell-stock/"/>
    <id>https://www.cylong.com/blog/2025/08/21/best-time-to-buy-and-sell-stock/</id>
    <published>2025-08-21T03:05:21.000Z</published>
    <updated>2025-08-21T03:05:21.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给定一个数组 <code>prices</code> ，它的第 <code>i</code> 个元素 <code>prices[i]</code> 表示一支给定股票第 <code>i</code> 天的价格。</p><p>你只能选择 <strong>某一天</strong> 买入这只股票，并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润，返回 <code>0</code> 。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>prices = [7, 1, 5, 3, 6, 4]</code><br><strong>输出：</strong><code>5</code><br><strong>解释：</strong>在第 2 天（股票价格 = 1）的时候买入，在第 5 天（股票价格 = 6）的时候卖出，最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格；同时，你不能在买入前卖出股票。</p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>prices = [7, 6, 4, 3, 1]</code><br><strong>输出：</strong><code>0</code><br><strong>解释：</strong>在这种情况下, 没有交易完成, 所以最大利润为 0。</p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= prices.length &lt;= 10^5</code></li><li><code>0 &lt;= prices[i] &lt;= 10^4</code></li></ul><span id="more"></span><h1 id="贪心算法"><a href="#贪心算法" class="headerlink" title="贪心算法"></a>贪心算法</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>我们可以通过遍历数组一次来计算最大利润。在遍历过程中，记录当前遇到的最小价格，并计算当前价格与最小价格的差值（即利润），同时更新最大利润。</p><ul><li><strong>初始化：</strong>将最小价格设置为一个很大的值（如 <code>Integer.MAX_VALUE</code>），将最大利润设置为 <code>0</code>。</li><li><strong>遍历数组：</strong><ul><li>如果当前价格小于最小价格，更新最小价格。</li><li>否则，计算当前价格与最小价格的差值，如果大于当前最大利润，则更新最大利润。</li></ul></li><li><strong>返回最大利润</strong></li></ul><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">maxProfit</span><span class="params">(<span class="type">int</span>[] prices)</span> &#123;</span><br><span class="line">    <span class="comment">// 如果数组为空或长度为零，直接返回0</span></span><br><span class="line">    <span class="keyword">if</span> (prices == <span class="literal">null</span> || prices.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 初始化最小价格为最大整数，最大利润为0</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">minPrice</span> <span class="operator">=</span> Integer.MAX_VALUE;</span><br><span class="line">    <span class="type">int</span> <span class="variable">maxProfit</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 遍历价格数组</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; prices.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (prices[i] &lt; minPrice) &#123;</span><br><span class="line">            <span class="comment">// 如果当前价格小于最小价格，更新最小价格</span></span><br><span class="line">            minPrice = prices[i];</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (prices[i] - minPrice &gt; maxProfit) &#123;</span><br><span class="line">            <span class="comment">// 如果当前价格减去最小价格大于最大利润，更新最大利润</span></span><br><span class="line">            maxProfit = prices[i] - minPrice;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> maxProfit;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，其中 <code>n</code> 是数组的长度。我们只需要遍历数组一次。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，只使用了常数级别的额外空间。</li></ul><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/" title="121. 买卖股票的最佳时机 | 力扣（LeetCode）">121. 买卖股票的最佳时机 | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给定一个数组 &lt;code&gt;prices&lt;/code&gt; ，它的第 &lt;code&gt;i&lt;/code&gt; 个元素 &lt;code&gt;prices[i]&lt;/code&gt; 表示一支给定股票第 &lt;code&gt;i&lt;/code&gt; 天的价格。&lt;/p&gt;
&lt;p&gt;你只能选择 &lt;strong&gt;某一天&lt;/strong&gt; 买入这只股票，并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润，返回 &lt;code&gt;0&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;prices = [7, 1, 5, 3, 6, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;5&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;在第 2 天（股票价格 = 1）的时候买入，在第 5 天（股票价格 = 6）的时候卖出，最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格；同时，你不能在买入前卖出股票。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;prices = [7, 6, 4, 3, 1]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;0&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;在这种情况下, 没有交易完成, 所以最大利润为 0。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= prices.length &amp;lt;= 10^5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt;= prices[i] &amp;lt;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="贪心算法" scheme="https://www.cylong.com/tags/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>哈希表统计与桶排序筛选前 K 个高频元素（LeetCode 347）</title>
    <link href="https://www.cylong.com/blog/2025/08/19/top-k-frequent-elements/"/>
    <id>https://www.cylong.com/blog/2025/08/19/top-k-frequent-elements/</id>
    <published>2025-08-19T15:10:03.000Z</published>
    <updated>2025-08-19T15:10:03.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你一个整数数组 <code>nums</code> 和一个整数 <code>k</code> ，请你返回其中出现频率前 <code>k</code> 高的元素。你可以按 <strong>任意顺序</strong> 返回答案。</p><p>你所设计算法的时间复杂度 <strong>必须</strong> 优于 <code>O(n log n)</code> ，其中 <code>n</code> 是数组大小。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [1, 1, 1, 2, 2, 3]</code>, <code>k = 2</code><br><strong>输出：</strong><code>[1, 2]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [1]</code>, <code>k = 1</code><br><strong>输出：</strong><code>[1]</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= nums.length &lt;= 10^5</code></li><li><code>k</code> 的取值范围是 <code>[1, 数组中不相同的元素的个数]</code></li><li>题目数据保证答案唯一，换句话说，数组中前 <code>k</code> 个高频元素的集合是唯一的</li></ul><span id="more"></span><h1 id="哈希表-频率映射"><a href="#哈希表-频率映射" class="headerlink" title="哈希表 + 频率映射"></a>哈希表 + 频率映射</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>使用哈希表统计频率，然后通过映射频率到数字列表，最后从高到低遍历频率；</p><ol><li><strong>统计频率：</strong>使用哈希表记录每个数字出现的次数。</li><li><strong>频率映射：</strong>创建另一个哈希表，将频率作为键，对应的数字列表作为值。</li><li><strong>收集结果：</strong>从最高频率（最大可能为数组长度）向下遍历，收集数字直到收集到 <code>k</code> 个。</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] topKFrequent(<span class="type">int</span>[] nums, <span class="type">int</span> k) &#123;</span><br><span class="line">    <span class="comment">// 处理边界情况</span></span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤1：统计每个数字的出现次数</span></span><br><span class="line">    Map&lt;Integer, Integer&gt; numToCountMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="comment">// 使用 merge 方法统计次数，等价于 getOrDefault 和 put</span></span><br><span class="line">        <span class="comment">// numToCountMap.put(num, numToCountMap.getOrDefault(num, 0) + 1);</span></span><br><span class="line">        numToCountMap.merge(num, <span class="number">1</span>, Integer::sum);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤2：将频率映射到数字列表</span></span><br><span class="line">    Map&lt;Integer, List&lt;Integer&gt;&gt; countToNumMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;Integer, Integer&gt; entry : numToCountMap.entrySet()) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">        <span class="comment">// 如果该频率还没有列表，创建一个新列表</span></span><br><span class="line">        <span class="keyword">if</span> (!countToNumMap.containsKey(count)) &#123;</span><br><span class="line">            countToNumMap.put(count, <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 将数字添加到对应频率的列表中</span></span><br><span class="line">        countToNumMap.get(count).add(num);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤3：从最高频率开始收集数字</span></span><br><span class="line">    List&lt;Integer&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="comment">// 频率最大可能为数组长度，从高到低遍历</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> nums.length; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="keyword">if</span> (countToNumMap.containsKey(i)) &#123;</span><br><span class="line">            List&lt;Integer&gt; numList = countToNumMap.get(i);</span><br><span class="line">            <span class="keyword">for</span> (Integer num : numList) &#123;</span><br><span class="line">                res.add(num);</span><br><span class="line">                <span class="comment">// 如果已收集到 k 个数字，转换为数组返回</span></span><br><span class="line">                <span class="keyword">if</span> (res.size() == k) &#123;</span><br><span class="line">                    <span class="keyword">return</span> res.stream().mapToInt(Integer::intValue).toArray();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，其中 <code>n</code> 是数组的长度。统计频率需要 <code>O(n)</code>，映射频率需要 <code>O(n)</code>，收集结果时最坏情况需要遍历所有频率（从 <code>n</code> 到 <code>0</code>），但每个数字只被处理一次，因此总体是 <code>O(n)</code>。</li><li><strong>空间复杂度：</strong><code>O(n)</code>，需要两个哈希表来存储数字和频率的映射关系，最坏情况下每个数字的频率都不同，因此需要 <code>O(n)</code> 空间。</li></ul><h1 id="哈希表-桶排序"><a href="#哈希表-桶排序" class="headerlink" title="哈希表 + 桶排序"></a>哈希表 + 桶排序</h1><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><p>将数字按频率分配到不同的桶中，然后从高频率桶开始收集元素。思路和方法一一致，这里其实只是把代码做了一些优化</p><ol><li><strong>统计频率：</strong>使用哈希表记录每个数字出现的次数。</li><li><strong>桶排序：</strong>创建一个桶数组，索引表示频率，每个桶存储具有该频率的数字列表。（<strong>与方法一区别：</strong><code>Map</code> 改为数组）</li><li><strong>收集结果：</strong>从最高频率桶开始向下遍历，收集数字直到收集到 <code>k</code> 个。（<strong>与方法一区别：</strong> 频率最大由数组长度优化为统计后的最大值）</li></ol><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] topKFrequent(<span class="type">int</span>[] nums, <span class="type">int</span> k) &#123;</span><br><span class="line">    <span class="comment">// 处理边界情况</span></span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤1：统计每个数字的出现次数</span></span><br><span class="line">    Map&lt;Integer, Integer&gt; numToCountMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="comment">// 使用 merge 方法统计次数，等价于 getOrDefault 和 put</span></span><br><span class="line">        <span class="comment">// numToCountMap.put(num, numToCountMap.getOrDefault(num, 0) + 1);</span></span><br><span class="line">        <span class="comment">// 1-&gt;3, 2-&gt;2, 3-&gt;1</span></span><br><span class="line">        numToCountMap.merge(num, <span class="number">1</span>, Integer::sum);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤2：创建桶数组，索引代表频率</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">maxCnt</span> <span class="operator">=</span> Collections.max(numToCountMap.values()); <span class="comment">// 获取最大频率</span></span><br><span class="line">    List&lt;Integer&gt;[] buckets = <span class="keyword">new</span> <span class="title class_">ArrayList</span>[maxCnt + <span class="number">1</span>];  <span class="comment">// 桶数组，从 0 到 maxCnt</span></span><br><span class="line">    Arrays.setAll(buckets, i -&gt; <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;());       <span class="comment">// 初始化每个桶为空列表</span></span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;Integer, Integer&gt; entry : numToCountMap.entrySet()) &#123;</span><br><span class="line">        <span class="comment">// 将数字添加到对应频率的桶中</span></span><br><span class="line">        <span class="comment">// 1-&gt;[3], 2-&gt;[2], 3-&gt;[1]</span></span><br><span class="line">        buckets[entry.getValue()].add(entry.getKey());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 步骤3：从最高频率桶开始收集数字</span></span><br><span class="line">    <span class="type">int</span>[] res = <span class="keyword">new</span> <span class="title class_">int</span>[k];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> buckets.length - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="keyword">for</span> (Integer num : buckets[i]) &#123;</span><br><span class="line">            res[--k] = num;</span><br><span class="line">            <span class="keyword">if</span> (k == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> res;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">0</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，其中 <code>n</code> 是数组的长度。统计频率需要 <code>O(n)</code>，创建桶需要 <code>O(n)</code>，收集结果需要 <code>O(n)</code>（因为每个数字只被处理一次）。</li><li><strong>空间复杂度：</strong><code>O(n)</code>，需要哈希表存储频率，桶数组的空间最多为 <code>O(n)</code>。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>两种方法都是基于频率统计，但方法二使用桶排序更直观，效率也更高，因为避免了在方法一中可能的多余遍历。方法二的空间复杂度略高，但实际应用中差异不大。推荐使用方法二，代码更简洁且易于理解。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/top-k-frequent-elements/description/" title="347. 前 K 个高频元素 | 力扣（LeetCode）">347. 前 K 个高频元素 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/top-k-frequent-elements/solutions/3655287/tong-pai-xu-on-xian-xing-zuo-fa-pythonja-oqq2/" title="347. 前 K 个高频元素 | 题解 | 灵茶山艾府">347. 前 K 个高频元素 | 题解 | 灵茶山艾府</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你一个整数数组 &lt;code&gt;nums&lt;/code&gt; 和一个整数 &lt;code&gt;k&lt;/code&gt; ，请你返回其中出现频率前 &lt;code&gt;k&lt;/code&gt; 高的元素。你可以按 &lt;strong&gt;任意顺序&lt;/strong&gt; 返回答案。&lt;/p&gt;
&lt;p&gt;你所设计算法的时间复杂度 &lt;strong&gt;必须&lt;/strong&gt; 优于 &lt;code&gt;O(n log n)&lt;/code&gt; ，其中 &lt;code&gt;n&lt;/code&gt; 是数组大小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [1, 1, 1, 2, 2, 3]&lt;/code&gt;, &lt;code&gt;k = 2&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[1, 2]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [1]&lt;/code&gt;, &lt;code&gt;k = 1&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[1]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= nums.length &amp;lt;= 10^5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;k&lt;/code&gt; 的取值范围是 &lt;code&gt;[1, 数组中不相同的元素的个数]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;题目数据保证答案唯一，换句话说，数组中前 &lt;code&gt;k&lt;/code&gt; 个高频元素的集合是唯一的&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="哈希表" scheme="https://www.cylong.com/tags/%E5%93%88%E5%B8%8C%E8%A1%A8/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="分治" scheme="https://www.cylong.com/tags/%E5%88%86%E6%B2%BB/"/>
    
    <category term="堆" scheme="https://www.cylong.com/tags/%E5%A0%86/"/>
    
    <category term="桶" scheme="https://www.cylong.com/tags/%E6%A1%B6/"/>
    
  </entry>
  
  <entry>
    <title>基于分治思想的第 K 大元素查找实现（LeetCode 215）</title>
    <link href="https://www.cylong.com/blog/2025/08/18/kth-largest-element-in-an-array/"/>
    <id>https://www.cylong.com/blog/2025/08/18/kth-largest-element-in-an-array/</id>
    <published>2025-08-18T15:14:00.000Z</published>
    <updated>2025-08-18T15:14:00.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给定整数数组 <code>nums</code> 和整数 <code>k</code>，请返回数组中第 <code>k</code> 个最大的元素。请注意，你需要找的是数组排序后的第 <code>k</code> 个最大的元素，而不是第 <code>k</code> 个不同的元素。</p><p>你必须设计并实现时间复杂度为 <code>O(n)</code> 的算法解决此问题。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>[3, 2, 1, 5, 6, 4]</code>, <code>k = 2</code><br><strong>输出：</strong><code>5</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>[3, 2, 3, 1, 2, 4, 5, 5, 6]</code>, <code>k = 4</code><br><strong>输出：</strong><code>4</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= k &lt;= nums.length &lt;= 10^5</code></li><li><code>-10^4 &lt;= nums[i] &lt;= 10^4</code></li></ul><span id="more"></span><h1 id="直接排序"><a href="#直接排序" class="headerlink" title="直接排序"></a>直接排序</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>将数组升序排序后，第 <code>k</code> 大的元素位于索引 <code>nums.length - k</code> 处。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Arrays;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findKthLargest</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    Arrays.sort(nums);            <span class="comment">// 升序排序</span></span><br><span class="line">    <span class="keyword">return</span> nums[nums.length - k]; <span class="comment">// 返回倒数第k个元素</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n log n)</code>，由排序算法决定。</li><li><strong>空间复杂度：</strong><code>O(1)</code>（忽略排序的栈空间开销）。</li></ul><h1 id="快速选择（分治）"><a href="#快速选择（分治）" class="headerlink" title="快速选择（分治）"></a>快速选择（分治）</h1><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><p>利用快速排序的分区思想：</p><ol><li>随机选择基准值 <code>pivot</code>。</li><li>将数组分为三部分：<code>big</code>（大于 <code>pivot</code>）、<code>small</code>（小于 <code>pivot</code>）和等于 <code>pivot</code> 的元素。</li><li>根据 <code>k</code> 与 <code>big.size()</code> 的关系递归处理：<ul><li>若 <code>k &lt;= big.size()</code>：第 <code>k</code> 大元素在 <code>big</code> 中。</li><li>若 <code>k &gt; nums.size() - small.size()</code>：第 <code>k</code> 大元素在 <code>small</code> 中（需调整 <code>k</code> 值）。</li><li>否则，<code>pivot</code> 即为目标值。</li></ul></li></ol><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findKthLargest</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    List&lt;Integer&gt; numList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        numList.add(num);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> quickSelect(numList, k);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">quickSelect</span><span class="params">(List&lt;Integer&gt; nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    <span class="comment">// 随机选择基准值</span></span><br><span class="line">    <span class="type">Random</span> <span class="variable">rand</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">    <span class="type">int</span> <span class="variable">pivot</span> <span class="operator">=</span> nums.get(rand.nextInt(nums.size()));</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 划分大于、小于基准值的元素</span></span><br><span class="line">    List&lt;Integer&gt; big = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    List&lt;Integer&gt; small = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="keyword">if</span> (num &gt; pivot) &#123;</span><br><span class="line">            big.add(num);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (num &lt; pivot) &#123;</span><br><span class="line">            small.add(num();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (k &lt;= big.size()) &#123;</span><br><span class="line">        <span class="comment">// 在 big 中递归查找</span></span><br><span class="line">        <span class="keyword">return</span> quickSelect(big, k);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums.size() - small.size() &lt; k) &#123;</span><br><span class="line">        <span class="comment">// 调整 k 值后在 small 中查找</span></span><br><span class="line">        <span class="keyword">return</span> quickSelect(small, k - (nums.size() - small.size()));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// pivot 是第 k 大元素</span></span><br><span class="line">    <span class="keyword">return</span> pivot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong>平均 <code>O(n)</code>，最坏 <code>O(n²)</code>（随机化避免最坏情况）。</li><li><strong>空间复杂度：</strong><code>O(n)</code>（递归栈和列表存储）。</li></ul><h1 id="原地快速选择（空间优化）"><a href="#原地快速选择（空间优化）" class="headerlink" title="原地快速选择（空间优化）"></a>原地快速选择（空间优化）</h1><h2 id="核心思路-2"><a href="#核心思路-2" class="headerlink" title="核心思路"></a>核心思路</h2><p>在方法二基础上优化空间：</p><ol><li>目标位置 <code>target = nums.length - k</code>（升序后第 <code>target</code> 个元素）。</li><li>分区时随机选择基准值并交换到末尾。</li><li>使用双指针分区：<ul><li><code>i</code> 标记小于基准值的边界。</li><li>遍历数组，将小于基准值的元素交换到 <code>i</code> 左侧。</li></ul></li><li>根据分区结果递归左/右子数组。</li></ol><h2 id="代码实现-2"><a href="#代码实现-2" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">findKthLargest</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> k)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> nums.length;</span><br><span class="line">    <span class="type">int</span> <span class="variable">target</span> <span class="operator">=</span> n - k; <span class="comment">// 升序排序后第 target 个元素是第 k 大</span></span><br><span class="line">    <span class="keyword">return</span> quickSelect(nums, <span class="number">0</span>, n - <span class="number">1</span>, target);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="title function_">quickSelect</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> left, <span class="type">int</span> right, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">    <span class="comment">// 递归终止</span></span><br><span class="line">    <span class="keyword">if</span> (left == right) &#123;</span><br><span class="line">        <span class="keyword">return</span> nums[left];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 随机选择基准值并交换到末尾</span></span><br><span class="line">    <span class="type">Random</span> <span class="variable">rand</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">    <span class="type">int</span> <span class="variable">pivotIdx</span> <span class="operator">=</span> left + rand.nextInt(right - left + <span class="number">1</span>);</span><br><span class="line">    swap(nums, pivotIdx, right);</span><br><span class="line">    <span class="type">int</span> <span class="variable">pivot</span> <span class="operator">=</span> nums[right];</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 分区操作</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> left; <span class="comment">// i 左侧元素均 &lt; pivot</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> left; j &lt; right; j++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (nums[j] &lt; pivot) &#123;</span><br><span class="line">            swap(nums, i, j);</span><br><span class="line">            i++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    swap(nums, i, right); <span class="comment">// 将基准值放到正确位置</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (i == target) &#123;</span><br><span class="line">        <span class="keyword">return</span> nums[i]; <span class="comment">// 找到目标</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (i &lt; target) &#123;</span><br><span class="line">        <span class="keyword">return</span> quickSelect(nums, i + <span class="number">1</span>, right, target); <span class="comment">// 递归右子数组</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> quickSelect(nums, left, i - <span class="number">1</span>, target); <span class="comment">// 递归左子数组</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">swap</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> i, <span class="type">int</span> j)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">temp</span> <span class="operator">=</span> nums[i];</span><br><span class="line">    nums[i] = nums[j];</span><br><span class="line">    nums[j] = temp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析-2"><a href="#复杂度分析-2" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong>平均 <code>O(n)</code>，最坏 <code>O(n²)</code>（随机化避免最坏情况）。</li><li><strong>空间复杂度：</strong><code>O(1)</code>（原地分区），递归栈平均 <code>O(log n)</code>。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><table><thead><tr><th>解法</th><th>时间复杂度</th><th>空间复杂度</th><th>适用场景</th></tr></thead><tbody><tr><td>直接排序</td><td>O(n log n)</td><td>O(1)</td><td>快速实现</td></tr><tr><td>快速选择（分治）</td><td>平均 O(n)</td><td>O(n)</td><td>无需严格空间约束</td></tr><tr><td>原地快速选择</td><td>平均 O(n)</td><td>O(log n)</td><td>空间效率最优</td></tr></tbody></table><p>实际应用中，原地快速选择综合性能最佳，尤其适合处理大规模数据。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/kth-largest-element-in-an-array/description/" title="215. 数组中的第K个最大元素 | 力扣（LeetCode）">215. 数组中的第K个最大元素 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/kth-largest-element-in-an-array/solutions/2361969/215-shu-zu-zhong-de-di-k-ge-zui-da-yuan-d786p/" title="215. 数组中的第K个最大元素 | 题解 | Krahets">215. 数组中的第K个最大元素 | 题解 | Krahets</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给定整数数组 &lt;code&gt;nums&lt;/code&gt; 和整数 &lt;code&gt;k&lt;/code&gt;，请返回数组中第 &lt;code&gt;k&lt;/code&gt; 个最大的元素。请注意，你需要找的是数组排序后的第 &lt;code&gt;k&lt;/code&gt; 个最大的元素，而不是第 &lt;code&gt;k&lt;/code&gt; 个不同的元素。&lt;/p&gt;
&lt;p&gt;你必须设计并实现时间复杂度为 &lt;code&gt;O(n)&lt;/code&gt; 的算法解决此问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;[3, 2, 1, 5, 6, 4]&lt;/code&gt;, &lt;code&gt;k = 2&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;5&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;[3, 2, 3, 1, 2, 4, 5, 5, 6]&lt;/code&gt;, &lt;code&gt;k = 4&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;4&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= k &amp;lt;= nums.length &amp;lt;= 10^5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-10^4 &amp;lt;= nums[i] &amp;lt;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="双指针" scheme="https://www.cylong.com/tags/%E5%8F%8C%E6%8C%87%E9%92%88/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="分治" scheme="https://www.cylong.com/tags/%E5%88%86%E6%B2%BB/"/>
    
    <category term="堆" scheme="https://www.cylong.com/tags/%E5%A0%86/"/>
    
  </entry>
  
  <entry>
    <title>递归操作解析嵌套编码字符串：数字与字符分段处理（LeetCode 394）</title>
    <link href="https://www.cylong.com/blog/2025/08/17/decode-string/"/>
    <id>https://www.cylong.com/blog/2025/08/17/decode-string/</id>
    <published>2025-08-17T12:50:34.000Z</published>
    <updated>2025-08-17T12:50:34.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给定一个经过编码的字符串，返回它解码后的字符串。编码规则为: <code>k[encoded_string]</code>，表示其中方括号内部的 <code>encoded_string</code> 正好重复 <code>k</code> 次。注意 <code>k</code> 保证为正整数。</p><p>你可以认为输入字符串总是有效的；输入字符串中没有额外的空格，且输入的方括号总是符合格式要求的。此外，你可以认为原始数据不包含数字，所有的数字只表示重复的次数 <code>k</code> ，例如不会出现像 <code>3a</code> 或 <code>2[4]</code> 的输入。</p><p>测试用例保证输出的长度不会超过 <code>10^5</code>。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;3[a]2[bc]&quot;</code><br><strong>输出：</strong><code>&quot;aaabcbc&quot;</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;3[a2[c]]&quot;</code><br><strong>输出：</strong><code>&quot;accaccacc&quot;</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;2[abc]3[cd]ef&quot;</code><br><strong>输出：</strong><code>&quot;abcabccdcdcdef&quot;</code></p></blockquote><p><strong>示例 4:</strong></p><blockquote><p><strong>输入：</strong><code>s = &quot;abc3[cd]xyz&quot;</code><br><strong>输出：</strong><code>&quot;abccdcdcdxyz&quot;</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= s.length &lt;= 30</code></li><li><code>s</code> 由小写英文字母、数字和方括号 <code>&#39;[]&#39;</code> 组成</li><li><code>s</code> 保证是一个有效的输入。</li><li><code>s</code> 中所有整数的取值范围为 <code>[1, 300] </code></li></ul><span id="more"></span><h1 id="递归"><a href="#递归" class="headerlink" title="递归"></a>递归</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>使用<strong>递归（DFS）</strong> 逐层解析嵌套括号：</p><ol><li>全局指针 <code>i</code>：记录当前处理的字符位置。</li><li>递归函数 <code>decode()</code>：<ul><li>遇到 <strong>字母</strong>：直接拼接到结果。</li><li>遇到 <strong>数字</strong>：计算重复次数 <code>k</code>。</li><li>遇到 <strong>左括号</strong> <code>[</code>：递归解析子字符串，并将结果重复 <code>k</code> 次拼接到当前结果。</li><li>遇到 <strong>右括号</strong> <code>]</code>：结束当前递归层，返回结果。</li></ul></li><li>重置 <code>k</code>：每次处理完 <code>k[encoded_string]</code> 后重置 <code>k</code>，避免影响后续数字。</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 全局指针</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">decodeString</span><span class="params">(String s)</span> &#123;</span><br><span class="line">    i = <span class="number">0</span>; <span class="comment">// 重置指针（避免多次调用干扰）</span></span><br><span class="line">    <span class="keyword">return</span> decode(s.toCharArray());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String <span class="title function_">decode</span><span class="params">(<span class="type">char</span>[] s)</span> &#123;</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">res</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">    <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 当前重复次数</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (i &lt; s.length) &#123;</span><br><span class="line">        <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> s[i];</span><br><span class="line">        i++;</span><br><span class="line">        <span class="keyword">if</span> (Character.isLetter(c)) &#123;</span><br><span class="line">            res.append(c); <span class="comment">// 字母直接拼接</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (Character.isDigit(c)) &#123;</span><br><span class="line">            k = k * <span class="number">10</span> + (c - <span class="string">&#x27;0&#x27;</span>); <span class="comment">// 计算重复次数（处理多位数）</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (c == <span class="string">&#x27;[&#x27;</span>) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">sub</span> <span class="operator">=</span> decode(s); <span class="comment">// 递归解析子字符串</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; k; j++) &#123;</span><br><span class="line">                res.append(sub); <span class="comment">// 重复拼接子字符串</span></span><br><span class="line">            &#125;</span><br><span class="line">            k = <span class="number">0</span>; <span class="comment">// 重置重复次数</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (c == <span class="string">&#x27;]&#x27;</span>) &#123;</span><br><span class="line">            <span class="keyword">break</span>; <span class="comment">// 结束当前递归层</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> res.toString();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，其中 <code>n</code> 是解码后字符串的长度。每个字符只处理一次，但重复拼接操作会增加时间。</li><li><strong>空间复杂度：</strong><code>O(m)</code>，<code>m</code> 是递归栈的最大深度（即嵌套括号层数）。</li></ul><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/decode-string/description/" title="394. 字符串解码 | 力扣（LeetCode）">394. 字符串解码 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/decode-string/solutions/3744428/di-gui-yong-zhan-mo-ni-di-gui-pythonjava-kcsv/" title="394. 字符串解码 | 题解 | 灵茶山艾府">394. 字符串解码 | 题解 | 灵茶山艾府</a><br><a href="https://leetcode.cn/problems/decode-string/solutions/19447/decode-string-fu-zhu-zhan-fa-di-gui-fa-by-jyd/" title="394. 字符串解码 | 题解 | Krahets">394. 字符串解码 | 题解 | Krahets</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给定一个经过编码的字符串，返回它解码后的字符串。编码规则为: &lt;code&gt;k[encoded_string]&lt;/code&gt;，表示其中方括号内部的 &lt;code&gt;encoded_string&lt;/code&gt; 正好重复 &lt;code&gt;k&lt;/code&gt; 次。注意 &lt;code&gt;k&lt;/code&gt; 保证为正整数。&lt;/p&gt;
&lt;p&gt;你可以认为输入字符串总是有效的；输入字符串中没有额外的空格，且输入的方括号总是符合格式要求的。此外，你可以认为原始数据不包含数字，所有的数字只表示重复的次数 &lt;code&gt;k&lt;/code&gt; ，例如不会出现像 &lt;code&gt;3a&lt;/code&gt; 或 &lt;code&gt;2[4]&lt;/code&gt; 的输入。&lt;/p&gt;
&lt;p&gt;测试用例保证输出的长度不会超过 &lt;code&gt;10^5&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;3[a]2[bc]&amp;quot;&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;&amp;quot;aaabcbc&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;3[a2[c]]&amp;quot;&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;&amp;quot;accaccacc&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;2[abc]3[cd]ef&amp;quot;&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;&amp;quot;abcabccdcdcdef&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 4:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;s = &amp;quot;abc3[cd]xyz&amp;quot;&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;&amp;quot;abccdcdcdxyz&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= s.length &amp;lt;= 30&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; 由小写英文字母、数字和方括号 &lt;code&gt;&amp;#39;[]&amp;#39;&lt;/code&gt; 组成&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; 保证是一个有效的输入。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s&lt;/code&gt; 中所有整数的取值范围为 &lt;code&gt;[1, 300] &lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="栈" scheme="https://www.cylong.com/tags/%E6%A0%88/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>双栈结构实现最小栈功能：同步记录最小值（LeetCode 155）</title>
    <link href="https://www.cylong.com/blog/2025/08/16/min-stack/"/>
    <id>https://www.cylong.com/blog/2025/08/16/min-stack/</id>
    <published>2025-08-16T07:48:02.000Z</published>
    <updated>2025-08-16T07:48:02.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>设计一个支持 <code>push</code> ，<code>pop</code> ，<code>top</code> 操作，并能在常数时间内检索到最小元素的栈。<br>实现 <code>MinStack</code> 类:</p><ul><li><code>MinStack()</code> 初始化堆栈对象。</li><li><code>void push(int val)</code> 将元素 <code>val</code> 推入堆栈。</li><li><code>void pop()</code> 删除堆栈顶部的元素。</li><li><code>int top()</code> 获取堆栈顶部的元素。</li><li><code>int getMin()</code> 获取堆栈中的最小元素。</li></ul><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><br><code>[&quot;MinStack&quot;, &quot;push&quot;, &quot;push&quot;, &quot;push&quot;, &quot;getMin&quot;, &quot;pop&quot;, &quot;top&quot;, &quot;getMin&quot;]</code><br><code>[[], [-2], [0], [-3], [], [], [], []]</code><br><strong>输出：</strong><code>[null, null, null, null, -3, null, 0, -2]</code><br><strong>解释：</strong><br>MinStack minStack = new MinStack();<br>minStack.push(-2);<br>minStack.push(0);<br>minStack.push(-3);<br>minStack.getMin();   –&gt; 返回 -3.<br>minStack.pop();<br>minStack.top();      –&gt; 返回 0.<br>minStack.getMin();   –&gt; 返回 -2.</p></blockquote><p><strong>提示:</strong></p><ul><li><code>-2^31 &lt;= val &lt;= 2^31 - 1</code></li><li><code>pop</code>、<code>top</code> 和 <code>getMin</code> 操作总是在 非空栈 上调用</li><li><code>push</code>, <code>pop</code>, <code>top</code>, 和 <code>getMin</code> 最多被调用 <code>3 * 10^4</code> 次</li></ul><span id="more"></span><h1 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h1><p>要设计一个支持常数时间内获取最小元素的栈，我们可以使用两个栈：</p><ol><li><strong>主栈（stack）：</strong>用于存储所有元素，支持常规的栈操作。</li><li><strong>最小栈（minStack）：</strong>用于存储当前主栈中每个状态下的最小值。</li></ol><h2 id="关键操作"><a href="#关键操作" class="headerlink" title="关键操作"></a>关键操作</h2><ul><li><strong>push 操作：</strong><ul><li>将元素推入主栈。</li><li>如果最小栈为空，或者新元素小于等于最小栈的栈顶元素，则将该元素也推入最小栈。</li></ul></li><li><strong>pop 操作：</strong><ul><li>弹出主栈的栈顶元素。</li><li>如果弹出的元素等于最小栈的栈顶元素，则同时弹出最小栈的栈顶元素（表示当前最小值已从主栈移除）。</li></ul></li><li><strong>top 操作：</strong>直接返回主栈的栈顶元素。</li><li><strong>getMin 操作：</strong>直接返回最小栈的栈顶元素（即当前栈中的最小元素）。</li></ul><h2 id="为什么使用“小于等于”？"><a href="#为什么使用“小于等于”？" class="headerlink" title="为什么使用“小于等于”？"></a>为什么使用“小于等于”？</h2><p>当新元素等于最小栈的栈顶元素时，也需要将其推入最小栈。这样可以确保当多个相同的最小值存在时，最小栈中也有多个相同的值，避免在 <code>pop</code> 操作时提前将最小值移除。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MinStack</span> &#123;</span><br><span class="line">    <span class="comment">// 注意不要使用 Stack 类，因为它继承自 Vector，是同步的，会导致一些性能问题</span></span><br><span class="line">    <span class="keyword">private</span> LinkedList&lt;Integer&gt; stack;    <span class="comment">// 主栈</span></span><br><span class="line">    <span class="keyword">private</span> LinkedList&lt;Integer&gt; minStack; <span class="comment">// 最小栈</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MinStack</span><span class="params">()</span> &#123;</span><br><span class="line">        stack = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        minStack = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> val)</span> &#123;</span><br><span class="line">        stack.push(val);</span><br><span class="line">        <span class="comment">// 如果最小栈为空，或者新元素小于等于最小栈栈顶元素，则推入最小栈</span></span><br><span class="line">        <span class="keyword">if</span> (minStack.isEmpty() || val &lt;= minStack.peek()) &#123;</span><br><span class="line">            minStack.push(val);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">popVal</span> <span class="operator">=</span> stack.pop();</span><br><span class="line">        <span class="comment">// 如果弹出的元素等于最小栈栈顶元素，则同时弹出最小栈栈顶元素</span></span><br><span class="line">        <span class="keyword">if</span> (popVal == minStack.peek()) &#123;</span><br><span class="line">            minStack.pop();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">top</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> stack.peek();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getMin</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> minStack.peek();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><ul><li><strong>push：</strong><code>O(1)</code>，每次操作只涉及常数次栈操作。</li><li><strong>pop：</strong><code>O(1)</code>，每次操作只涉及常数次栈操作。</li><li><strong>top：</strong><code>O(1)</code>，直接返回主栈栈顶元素。</li><li><strong>getMin：</strong><code>O(1)</code>，直接返回最小栈栈顶元素。</li></ul></li><li><strong>空间复杂度：</strong><code>O(n)</code>，最坏情况下需要两个栈存储所有元素，但最小栈在优化后通常存储元素较少。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>通过使用两个栈（主栈和最小栈），我们可以在常数时间内完成所有栈操作并获取当前栈中的最小元素。最小栈的维护确保了在每次操作后都能快速获取到最小值，而主栈则负责常规的栈功能。这种方法既高效又易于实现，是解决此类问题的经典方案。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/min-stack/description/" title="155. 最小栈 | 力扣（LeetCode）">155. 最小栈 | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;设计一个支持 &lt;code&gt;push&lt;/code&gt; ，&lt;code&gt;pop&lt;/code&gt; ，&lt;code&gt;top&lt;/code&gt; 操作，并能在常数时间内检索到最小元素的栈。&lt;br&gt;实现 &lt;code&gt;MinStack&lt;/code&gt; 类:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MinStack()&lt;/code&gt; 初始化堆栈对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void push(int val)&lt;/code&gt; 将元素 &lt;code&gt;val&lt;/code&gt; 推入堆栈。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void pop()&lt;/code&gt; 删除堆栈顶部的元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int top()&lt;/code&gt; 获取堆栈顶部的元素。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int getMin()&lt;/code&gt; 获取堆栈中的最小元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;br&gt;&lt;code&gt;[&amp;quot;MinStack&amp;quot;, &amp;quot;push&amp;quot;, &amp;quot;push&amp;quot;, &amp;quot;push&amp;quot;, &amp;quot;getMin&amp;quot;, &amp;quot;pop&amp;quot;, &amp;quot;top&amp;quot;, &amp;quot;getMin&amp;quot;]&lt;/code&gt;&lt;br&gt;&lt;code&gt;[[], [-2], [0], [-3], [], [], [], []]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[null, null, null, null, -3, null, 0, -2]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;&lt;br&gt;MinStack minStack = new MinStack();&lt;br&gt;minStack.push(-2);&lt;br&gt;minStack.push(0);&lt;br&gt;minStack.push(-3);&lt;br&gt;minStack.getMin();   –&amp;gt; 返回 -3.&lt;br&gt;minStack.pop();&lt;br&gt;minStack.top();      –&amp;gt; 返回 0.&lt;br&gt;minStack.getMin();   –&amp;gt; 返回 -2.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-2^31 &amp;lt;= val &amp;lt;= 2^31 - 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pop&lt;/code&gt;、&lt;code&gt;top&lt;/code&gt; 和 &lt;code&gt;getMin&lt;/code&gt; 操作总是在 非空栈 上调用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pop&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, 和 &lt;code&gt;getMin&lt;/code&gt; 最多被调用 &lt;code&gt;3 * 10^4&lt;/code&gt; 次&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="栈" scheme="https://www.cylong.com/tags/%E6%A0%88/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>两次二分查找定位有序数组元素边界（LeetCode 34）</title>
    <link href="https://www.cylong.com/blog/2025/08/02/find-first-and-last-position-of-element-in-sorted-array/"/>
    <id>https://www.cylong.com/blog/2025/08/02/find-first-and-last-position-of-element-in-sorted-array/</id>
    <published>2025-08-02T06:22:23.000Z</published>
    <updated>2025-08-02T06:22:23.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你一个按照非递减顺序排列的整数数组 <code>nums</code>，和一个目标值 <code>target</code>。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 <code>target</code>，返回 <code>[-1, -1]</code>。</p><p>你必须设计并实现时间复杂度为 <code>O(log n)</code> 的算法解决此问题。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [5, 7, 7, 8, 8, 10]</code>, <code>target = 8</code><br><strong>输出：</strong><code>[3, 4]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [5, 7, 7, 8, 8, 10]</code>, <code>target = 6</code><br><strong>输出：</strong><code>[-1, -1]</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>nums = []</code>, <code>target = 0</code><br><strong>输出：</strong><code>[-1, -1]</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>0 &lt;= nums.length &lt;= 10^5</code></li><li><code>-10^9 &lt;= nums[i] &lt;= 10^9</code></li><li><code>nums</code> 是一个非递减数组</li><li><code>-10^9 &lt;= target &lt;= 10^9</code></li></ul><span id="more"></span><h1 id="一次二分查找-线性扩展"><a href="#一次二分查找-线性扩展" class="headerlink" title="一次二分查找 + 线性扩展"></a>一次二分查找 + 线性扩展</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>使用二分查找找到目标值，然后向左右两边扩展，直到找到边界。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span>[] searchRange(<span class="type">int</span>[] nums, <span class="type">int</span> target) &#123;</span><br><span class="line">    <span class="comment">// 处理空数组的情况</span></span><br><span class="line">    <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;-<span class="number">1</span>, -<span class="number">1</span>&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">right</span> <span class="operator">=</span> nums.length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="comment">// 计算中间位置，防止整数溢出</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (nums[mid] == target) &#123;</span><br><span class="line">            <span class="comment">// 找到目标值，继续向两边搜索边界</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">start</span> <span class="operator">=</span> mid, end = mid;</span><br><span class="line">            <span class="comment">// 向左搜索起始位置</span></span><br><span class="line">            <span class="keyword">while</span> (start &gt; left &amp;&amp; nums[start - <span class="number">1</span>] == target) &#123;</span><br><span class="line">                start--;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 向右搜索结束位置</span></span><br><span class="line">            <span class="keyword">while</span> (end &lt; right &amp;&amp; nums[end + <span class="number">1</span>] == target) &#123;</span><br><span class="line">                end++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[] &#123;start, end&#125;;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (nums[mid] &lt; target) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[] &#123;-<span class="number">1</span>, -<span class="number">1</span>&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong>平均情况 <code>O(logn)</code>，但在最坏情况下（整个数组都是目标值）会退化为 <code>O(n)</code>。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，仅使用常数空间存储变量。</li></ul><h1 id="两次二分查找"><a href="#两次二分查找" class="headerlink" title="两次二分查找"></a>两次二分查找</h1><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><p>分别使用二分查找寻找左边界和右边界：</p><ul><li><strong>查找左边界：</strong>第一个等于目标值的位置<ul><li>初始化左右指针 <code>left = 0</code>, <code>right = nums.length - 1</code></li><li>当 <code>left &lt;= right</code> 时循环：<ul><li>计算中间位置 <code>mid = left + (right - left) / 2</code>（防止整数溢出）</li><li>如果 <code>nums[mid] &gt;= target</code>，则目标可能在左半部分，调整右指针 <code>right = mid - 1</code></li><li>否则调整左指针 <code>left = mid + 1</code></li></ul></li><li>循环结束后，检查 <code>left</code> 是否在数组范围内且对应值等于 <code>target</code></li></ul></li><li><strong>查找右边界：</strong>最后一个等于目标值的位置<ul><li>初始化左右指针 <code>left = 0</code>, <code>right = nums.length - 1</code></li><li>当 <code>left &lt;= right</code> 时循环：<ul><li>计算中间位置 <code>mid = left + (right - left) / 2</code>（防止整数溢出）</li><li>如果 <code>nums[mid] &lt;= target</code>，则目标可能在右半部分，调整左指针 <code>left = mid + 1</code></li><li>否则调整右指针 <code>right = mid - 1</code></li></ul></li><li>循环结束后，检查 <code>right</code> 是否在数组范围内且对应值等于 <code>target</code></li></ul></li></ul><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span>[] searchRange(<span class="type">int</span>[] nums, <span class="type">int</span> target) &#123;</span><br><span class="line">        <span class="comment">// 处理空数组的情况</span></span><br><span class="line">        <span class="keyword">if</span> (nums == <span class="literal">null</span> || nums.length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;-<span class="number">1</span>, -<span class="number">1</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="type">int</span> <span class="variable">leftBound</span> <span class="operator">=</span> findLeftBound(nums, target);</span><br><span class="line">        <span class="type">int</span> <span class="variable">rightBound</span> <span class="operator">=</span> findRightBound(nums, target);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">int</span>[]&#123;leftBound, rightBound&#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查找左边界：第一个等于 target 的位置</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">findLeftBound</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &gt;= target) &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>; <span class="comment">// 向左搜索</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 检查 left 是否在数组范围内且等于 target</span></span><br><span class="line">        <span class="keyword">if</span> (left &lt; nums.length &amp;&amp; nums[left] == target) &#123;</span><br><span class="line">            <span class="keyword">return</span> left;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 查找右边界：最后一个等于 target 的位置</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">findRightBound</span><span class="params">(<span class="type">int</span>[] nums, <span class="type">int</span> target)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = nums.length - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">            <span class="keyword">if</span> (nums[mid] &lt;= target) &#123;</span><br><span class="line">                left = mid + <span class="number">1</span>; <span class="comment">// 向右搜索</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                right = mid - <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 检查 right 是否在数组范围内且等于 target</span></span><br><span class="line">        <span class="keyword">if</span> (right &gt;= <span class="number">0</span> &amp;&amp; nums[right] == target) &#123;</span><br><span class="line">            <span class="keyword">return</span> right;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(log n)</code>。两次二分查找，每次时间复杂度为 <code>O(log n)</code>。</li><li><strong>空间复杂度：</strong><code>O(1)</code>。仅使用常数空间存储变量。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><table><thead><tr><th>解法</th><th>时间复杂度</th><th>空间复杂度</th><th>优点</th></tr></thead><tbody><tr><td>一次二分查找 + 线性扩展</td><td>O(n)（最坏）<br>O(log n)（最好）</td><td>O(1)</td><td>代码简单，但是受重复元素数量影响，最坏情况时间复杂度 O(n)</td></tr><tr><td>两次二分查找</td><td>O(log n)（所有情况）</td><td>O(1)</td><td>保证最坏情况下的性能，不受输入数据分布影响</td></tr></tbody></table><p>最终推荐两次二分查找方法，它严格满足题目要求，在各种情况下都能保持高效稳定的性能，是符合题目要求的解法。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/" title="34. 在排序数组中查找元素的第一个和最后一个位置 | 力扣（LeetCode）">34. 在排序数组中查找元素的第一个和最后一个位置 | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你一个按照非递减顺序排列的整数数组 &lt;code&gt;nums&lt;/code&gt;，和一个目标值 &lt;code&gt;target&lt;/code&gt;。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 &lt;code&gt;target&lt;/code&gt;，返回 &lt;code&gt;[-1, -1]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你必须设计并实现时间复杂度为 &lt;code&gt;O(log n)&lt;/code&gt; 的算法解决此问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [5, 7, 7, 8, 8, 10]&lt;/code&gt;, &lt;code&gt;target = 8&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[3, 4]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [5, 7, 7, 8, 8, 10]&lt;/code&gt;, &lt;code&gt;target = 6&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[-1, -1]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = []&lt;/code&gt;, &lt;code&gt;target = 0&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[-1, -1]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt;= nums.length &amp;lt;= 10^5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-10^9 &amp;lt;= nums[i] &amp;lt;= 10^9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nums&lt;/code&gt; 是一个非递减数组&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-10^9 &amp;lt;= target &amp;lt;= 10^9&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="二分查找" scheme="https://www.cylong.com/tags/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>链表数字相加的逆序计算：栈实现与进位处理（LeetCode 445）</title>
    <link href="https://www.cylong.com/blog/2025/07/25/add-two-numbers-ii/"/>
    <id>https://www.cylong.com/blog/2025/07/25/add-two-numbers-ii/</id>
    <published>2025-07-24T16:23:06.000Z</published>
    <updated>2025-07-24T16:23:06.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。你可以假设除了数字 0 之外，这两个数字都不会以零开头。</p><p><strong>示例 1:</strong></p><img src="/blog/2025/07/25/add-two-numbers-ii/445-image.png" class=""><blockquote><p><strong>输入：</strong><code>l1 = [7, 2, 4, 3], l2 = [5, 6, 4]</code><br><strong>输出：</strong><code>[7, 8, 0, 7]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>l1 = [2, 4, 3], l2 = [5, 6, 4]</code><br><strong>输出：</strong><code>[8, 0, 7]</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>l1 = [0], l2 = [0]</code><br><strong>输出：</strong><code>[0]</code></p></blockquote><p><strong>提示:</strong></p><ul><li>链表的长度范围为 <code>[1, 100]</code></li><li><code>0 &lt;= node.val &lt;= 9</code></li><li>输入数据保证链表代表的数字无前导 <code>0</code></li></ul><span id="more"></span><h1 id="栈辅助法"><a href="#栈辅助法" class="headerlink" title="栈辅助法"></a>栈辅助法</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>利用栈的 <strong>后进先出</strong> 特性实现逆序处理：</p><ol><li>遍历两个链表，将节点值分别压入两个栈。</li><li>同时弹出栈顶元素进行相加，处理进位。</li><li>使用头插法构建结果链表。</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">addTwoNumbers</span><span class="params">(ListNode l1, ListNode l2)</span> &#123;</span><br><span class="line">    Stack&lt;Integer&gt; stack1 = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    Stack&lt;Integer&gt; stack2 = <span class="keyword">new</span> <span class="title class_">Stack</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 将链表节点值压入栈中</span></span><br><span class="line">    <span class="keyword">while</span> (l1 != <span class="literal">null</span>) &#123;</span><br><span class="line">        stack1.push(l1.val);</span><br><span class="line">        l1 = l1.next;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (l2 != <span class="literal">null</span>) &#123;</span><br><span class="line">        stack2.push(l2.val);</span><br><span class="line">        l2 = l2.next;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="type">int</span> <span class="variable">carry</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 进位值</span></span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">resultHead</span> <span class="operator">=</span> <span class="literal">null</span>; <span class="comment">// 结果链表头节点</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 当栈非空或仍有进位时继续计算</span></span><br><span class="line">    <span class="keyword">while</span> (!stack1.isEmpty() || !stack2.isEmpty() || carry != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 获取栈顶元素（栈空则取0）</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">num1</span> <span class="operator">=</span> stack1.isEmpty() ? <span class="number">0</span> : stack1.pop();</span><br><span class="line">        <span class="type">int</span> <span class="variable">num2</span> <span class="operator">=</span> stack2.isEmpty() ? <span class="number">0</span> : stack2.pop();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 计算当前位的和</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> num1 + num2 + carry;</span><br><span class="line">        carry = sum / <span class="number">10</span>; <span class="comment">// 更新进位</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">digit</span> <span class="operator">=</span> sum % <span class="number">10</span>; <span class="comment">// 当前位的值</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 使用头插法构建结果链表</span></span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">newNode</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(digit);</span><br><span class="line">        newNode.next = resultHead;</span><br><span class="line">        resultHead = newNode;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> resultHead;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(max(m, n))</code>，其中 <code>m</code> 和 <code>n</code> 分别为两个链表的长度。</li><li><strong>空间复杂度：</strong><code>O(m + n)</code>，用于存储两个栈。</li></ul><h1 id="链表反转法"><a href="#链表反转法" class="headerlink" title="链表反转法"></a>链表反转法</h1><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><p>通过反转链表实现低位对齐：</p><ol><li>反转两个输入链表。</li><li>按照两数相加I的方法计算（低位对齐）</li><li>反转结果链表得到最终答案</li></ol><p>参考：</p><blockquote><p><a href="/blog/2019/11/25/add-two-numbers/" title="两数相加 I | 笑话人生">两数相加 I | 笑话人生</a><br><a href="/blog/2025/07/22/reverse-linked-list/" title="反转链表的指针操作与递归实现（LeetCode 206）| 笑话人生">反转链表的指针操作与递归实现（LeetCode 206）| 笑话人生</a></p></blockquote><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ListNode <span class="title function_">addTwoNumbers</span><span class="params">(ListNode l1, ListNode l2)</span> &#123;</span><br><span class="line">    <span class="comment">// 反转两个链表</span></span><br><span class="line">    l1 = reverseList(l1);</span><br><span class="line">    l2 = reverseList(l2);</span><br><span class="line">    <span class="comment">// 相加得到结果链表（低位在前）</span></span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">l3</span> <span class="operator">=</span> addTwo(l1, l2);</span><br><span class="line">    <span class="comment">// 反转结果链表得到高位在前</span></span><br><span class="line">    <span class="keyword">return</span> reverseList(l3);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 反转链表（LeetCode 206）</span></span><br><span class="line"><span class="keyword">private</span> ListNode <span class="title function_">reverseList</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">cur</span> <span class="operator">=</span> head, pre = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">while</span> (cur != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">tmp</span> <span class="operator">=</span> cur.next; <span class="comment">// 保存下一个节点</span></span><br><span class="line">        cur.next = pre;          <span class="comment">// 当前节点指向前一个节点</span></span><br><span class="line">        pre = cur;               <span class="comment">// 前节点后移</span></span><br><span class="line">        cur = tmp;               <span class="comment">// 当前节点后移</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> pre; <span class="comment">// 返回反转后的头节点</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 两数相加（LeetCode 2）</span></span><br><span class="line"><span class="keyword">private</span> ListNode <span class="title function_">addTwo</span><span class="params">(ListNode l1, ListNode l2)</span> &#123;</span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">dummy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ListNode</span>(<span class="number">0</span>); <span class="comment">// 虚拟头节点</span></span><br><span class="line">    <span class="type">ListNode</span> <span class="variable">cur</span> <span class="operator">=</span> dummy;</span><br><span class="line">    <span class="type">int</span> <span class="variable">carry</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// 进位值</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (l1 != <span class="literal">null</span> || l2 != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> (l1 != <span class="literal">null</span>) ? l1.val : <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> (l2 != <span class="literal">null</span>) ? l2.val : <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> x + y + carry;</span><br><span class="line">        </span><br><span class="line">        carry = sum / <span class="number">10</span>; <span class="comment">// 计算进位</span></span><br><span class="line">        cur.next = <span class="keyword">new</span> <span class="title class_">ListNode</span>(sum % <span class="number">10</span>); <span class="comment">// 创建新节点</span></span><br><span class="line">        cur = cur.next; <span class="comment">// 移动当前指针</span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 移动链表指针</span></span><br><span class="line">        <span class="keyword">if</span> (l1 != <span class="literal">null</span>) l1 = l1.next;</span><br><span class="line">        <span class="keyword">if</span> (l2 != <span class="literal">null</span>) l2 = l2.next;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理最后的进位</span></span><br><span class="line">    <span class="keyword">if</span> (carry &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        cur.next = <span class="keyword">new</span> <span class="title class_">ListNode</span>(carry);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dummy.next;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(max(m, n))</code>，反转链表和相加操作都是线性时间。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，仅使用固定数量的指针。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><table><thead><tr><th>解法</th><th>时间复杂度</th><th>空间复杂度</th><th>优点</th></tr></thead><tbody><tr><td>栈辅助法</td><td>O(max(m, n))</td><td>O(m + n)</td><td>逻辑清晰，但需要额外栈空间</td></tr><tr><td>链表反转法</td><td>O(max(m, n))</td><td>O(1)</td><td>空间更优，但修改了原始链表</td></tr></tbody></table><p>两种方法各有优劣：</p><ol><li><strong>栈辅助法：</strong>不修改原始链表，逻辑清晰，但需要额外空间存储栈。</li><li><strong>链表反转法：</strong>空间效率更高，但会修改原始链表结构。</li></ol><p>在实际应用中，如果原始链表不可修改，应选择栈辅助法；若空间效率是首要考虑因素，则链表反转法更优。两种方法的时间复杂度相同，都能高效解决该问题。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/add-two-numbers-ii/description/" title="445. 两数相加 II | 力扣（LeetCode）">445. 两数相加 II | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。你可以假设除了数字 0 之外，这两个数字都不会以零开头。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;img src=&quot;/blog/2025/07/25/add-two-numbers-ii/445-image.png&quot; class=&quot;&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;l1 = [7, 2, 4, 3], l2 = [5, 6, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[7, 8, 0, 7]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;l1 = [2, 4, 3], l2 = [5, 6, 4]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[8, 0, 7]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;l1 = [0], l2 = [0]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[0]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链表的长度范围为 &lt;code&gt;[1, 100]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt;= node.val &amp;lt;= 9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;输入数据保证链表代表的数字无前导 &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="链表" scheme="https://www.cylong.com/tags/%E9%93%BE%E8%A1%A8/"/>
    
    <category term="双指针" scheme="https://www.cylong.com/tags/%E5%8F%8C%E6%8C%87%E9%92%88/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="栈" scheme="https://www.cylong.com/tags/%E6%A0%88/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="回溯" scheme="https://www.cylong.com/tags/%E5%9B%9E%E6%BA%AF/"/>
    
    <category term="迭代" scheme="https://www.cylong.com/tags/%E8%BF%AD%E4%BB%A3/"/>
    
  </entry>
  
  <entry>
    <title>反转链表的指针操作与递归实现（LeetCode 206）</title>
    <link href="https://www.cylong.com/blog/2025/07/22/reverse-linked-list/"/>
    <id>https://www.cylong.com/blog/2025/07/22/reverse-linked-list/</id>
    <published>2025-07-22T15:52:44.000Z</published>
    <updated>2025-07-22T15:52:44.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你单链表的头节点 <code>head</code> ，请你反转链表，并返回反转后的链表。</p><p><strong>示例 1:</strong></p><img src="/blog/2025/07/22/reverse-linked-list/rev1ex1.jpg" class=""><blockquote><p><strong>输入：</strong><code>head = [1, 2, 3, 4, 5]</code><br><strong>输出：</strong><code>[5, 4, 3, 2, 1]</code></p></blockquote><p><strong>示例 2:</strong></p><img src="/blog/2025/07/22/reverse-linked-list/rev1ex2.jpg" class=""><blockquote><p><strong>输入：</strong><code>head = [1, 2]</code><br><strong>输出：</strong><code>[2, 1]</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>head = []</code><br><strong>输出：</strong><code>[]</code></p></blockquote><p><strong>提示:</strong></p><ul><li>链表中节点的数目范围是 <code>[0, 5000]</code></li><li><code>-5000 &lt;= Node.val &lt;= 5000</code></li></ul><span id="more"></span><h1 id="迭代法"><a href="#迭代法" class="headerlink" title="迭代法"></a>迭代法</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>通过遍历链表，逐个修改节点指向。使用双指针 <code>pre</code> 和 <code>cur</code>，每次将 <code>cur.next</code> 指向 <code>pre</code> 并同步移动指针，直到遍历完成。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> ListNode <span class="title function_">reverseList</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">cur</span> <span class="operator">=</span> head;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">pre</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">while</span> (cur != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">ListNode</span> <span class="variable">tmp</span> <span class="operator">=</span> cur.next; <span class="comment">// 暂存后继节点</span></span><br><span class="line">            cur.next = pre;          <span class="comment">// 反转当前节点的指针</span></span><br><span class="line">            pre = cur;               <span class="comment">// pre 移动至当前节点</span></span><br><span class="line">            cur = tmp;               <span class="comment">// cur 访问下一节点</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> pre; <span class="comment">// 返回新头节点</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="动态变化过程"><a href="#动态变化过程" class="headerlink" title="动态变化过程"></a>动态变化过程</h2><p>以链表 <code>1→2→3→NULL</code> 为例</p><p><strong>初始状态</strong><br><code>pre = null</code>, <code>cur = 1</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NULL ← ?    1 → 2 → 3 → NULL</span><br><span class="line"> ↑          ↑</span><br><span class="line">pre        cur</span><br></pre></td></tr></table></figure><p><strong>第一步</strong><br>暂存 <code>cur.next = 2</code> → 修改 <code>1.next = pre(null)</code> → 移动 <code>pre=1</code>, <code>cur=2</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NULL ← 1    2 → 3 → NULL</span><br><span class="line">       ↑    ↑</span><br><span class="line">      pre  cur</span><br></pre></td></tr></table></figure><p><strong>第二步</strong><br>暂存 <code>cur.next = 3</code> → 修改 <code>2.next = pre(1)</code> → 移动 <code>pre=2</code>, <code>cur=3</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NULL ← 1 ← 2    3 → NULL</span><br><span class="line">           ↑    ↑</span><br><span class="line">          pre  cur</span><br></pre></td></tr></table></figure><p><strong>第三步</strong><br>暂存 <code>cur.next = NULL</code> → 修改 <code>3.next = pre(2)</code> → 移动 <code>pre=3</code>, <code>cur=NULL</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">NULL ← 1 ← 2 ← 3    NULL</span><br><span class="line">               ↑     ↑</span><br><span class="line">              pre   cur</span><br></pre></td></tr></table></figure><p><strong>结果</strong><br>返回 <code>pre = 3</code>，新链表为 <code>3→2→1→NULL</code>。</p><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，遍历链表一次。</li><li><strong>空间复杂度：</strong><code>O(1)</code>，仅使用常量额外空间。</li></ul><h1 id="递归法"><a href="#递归法" class="headerlink" title="递归法"></a>递归法</h1><h2 id="核心思路-1"><a href="#核心思路-1" class="headerlink" title="核心思路"></a>核心思路</h2><p>递归到链表末端，回溯时逐层反转节点指向：</p><ol><li>递归终止条件：当前节点为尾节点（<code>head.next == null</code>）。</li><li>回溯过程中，将下一节点的 <code>next</code> 指向当前节点，并断开原指向。</li></ol><h2 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> ListNode <span class="title function_">reverseList</span><span class="params">(ListNode head)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (head == <span class="literal">null</span> || head.next == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> head; <span class="comment">// 终止条件：返回尾节点作为新头节点</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">ListNode</span> <span class="variable">newHead</span> <span class="operator">=</span> reverseList(head.next); <span class="comment">// 递归至下一层</span></span><br><span class="line">        head.next.next = head; <span class="comment">// 反转指向：下一节点指向当前节点</span></span><br><span class="line">        head.next = <span class="literal">null</span>;      <span class="comment">// 断开当前节点原指向</span></span><br><span class="line">        <span class="keyword">return</span> newHead;        <span class="comment">// 始终返回新头节点</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="动态变化过程-1"><a href="#动态变化过程-1" class="headerlink" title="动态变化过程"></a>动态变化过程</h2><p>以链表 <code>1→2→3→NULL</code> 为例</p><p><strong>递归至最深层</strong><br>当 <code>head=3</code> 时满足终止条件，返回 <code>3</code> 作为 <code>newHead</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1 → 2 → 3 → NULL</span><br><span class="line">        ↑</span><br><span class="line">      head (返回3)</span><br></pre></td></tr></table></figure><p><strong>回溯第一层</strong></p><ul><li><code>head=2</code></li><li>执行 <code>head.next.next = head</code> → <code>3.next = 2</code></li><li>执行 <code>head.next = null</code> → <code>2.next = NULL</code><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1 → 2    3 → 2 → NULL   // 3指向2，2指向NULL</span><br><span class="line">    ↑    ↑</span><br><span class="line">  head newHead(3)</span><br></pre></td></tr></table></figure></li></ul><p><strong>回溯第二层</strong></p><ul><li><code>head=1</code></li><li>执行 <code>head.next.next = head</code> → <code>2.next = 1</code></li><li>执行 <code>head.next = null</code> → <code>1.next = NULL</code><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">  1    2 → 1 → NULL   // 2指向1，1指向NULL</span><br><span class="line">  ↑    ↑</span><br><span class="line">head newHead(3)</span><br></pre></td></tr></table></figure></li></ul><p><strong>最终结果</strong><br>返回 <code>newHead = 3</code>，链表变为 <code>3→2→1→NULL</code>。</p><h2 id="复杂度分析-1"><a href="#复杂度分析-1" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，递归深度为链表长度。</li><li><strong>空间复杂度：</strong><code>O(n)</code>，递归栈深度为链表长度。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><table><thead><tr><th>解法</th><th>时间复杂度</th><th>空间复杂度</th><th>优点</th></tr></thead><tbody><tr><td>迭代法</td><td>O(n)</td><td>O(1)</td><td>空间复杂度低</td></tr><tr><td>递归法</td><td>O(n)</td><td>O(n)</td><td>代码简洁</td></tr></tbody></table><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/reverse-linked-list/description/" title="206. 反转链表 | 力扣（LeetCode）">206. 反转链表 | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你单链表的头节点 &lt;code&gt;head&lt;/code&gt; ，请你反转链表，并返回反转后的链表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;img src=&quot;/blog/2025/07/22/reverse-linked-list/rev1ex1.jpg&quot; class=&quot;&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;head = [1, 2, 3, 4, 5]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[5, 4, 3, 2, 1]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;img src=&quot;/blog/2025/07/22/reverse-linked-list/rev1ex2.jpg&quot; class=&quot;&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;head = [1, 2]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[2, 1]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;head = []&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链表中节点的数目范围是 &lt;code&gt;[0, 5000]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-5000 &amp;lt;= Node.val &amp;lt;= 5000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="链表" scheme="https://www.cylong.com/tags/%E9%93%BE%E8%A1%A8/"/>
    
    <category term="双指针" scheme="https://www.cylong.com/tags/%E5%8F%8C%E6%8C%87%E9%92%88/"/>
    
    <category term="递归" scheme="https://www.cylong.com/tags/%E9%80%92%E5%BD%92/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="回溯" scheme="https://www.cylong.com/tags/%E5%9B%9E%E6%BA%AF/"/>
    
    <category term="迭代" scheme="https://www.cylong.com/tags/%E8%BF%AD%E4%BB%A3/"/>
    
  </entry>
  
  <entry>
    <title>全排列问题的递归回溯解法实现（LeetCode 46）</title>
    <link href="https://www.cylong.com/blog/2025/07/15/permutations/"/>
    <id>https://www.cylong.com/blog/2025/07/15/permutations/</id>
    <published>2025-07-15T14:54:28.000Z</published>
    <updated>2025-07-15T14:54:28.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给定一个不含重复数字的数组 <code>nums</code> ，返回其所有可能的全排列 。你可以按任意顺序返回答案。</p><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [1, 2, 3]</code><br><strong>输出：</strong><code>[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [0, 1]</code><br><strong>输出：</strong><code>[[0, 1], [1, 0]]</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>nums = [1]</code><br><strong>输出：</strong><code>[[1]]</code></p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= nums.length &lt;= 6</code></li><li><code>-10 &lt;= nums[i] &lt;= 10</code></li><li><code>nums</code> 中的所有整数互不相同</li></ul><span id="more"></span><h1 id="回溯算法（DFS）"><a href="#回溯算法（DFS）" class="headerlink" title="回溯算法（DFS）"></a>回溯算法（DFS）</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>全排列问题可以通过回溯算法解决。核心思想是：每次从数组中选取一个未被使用的元素加入当前路径，当路径长度等于数组长度时，将当前路径加入结果集。之后通过回溯撤销选择，尝试其他可能的元素。</p><ol><li>初始化结果列表 <code>ans</code> 和当前路径列表 <code>t</code></li><li>创建布尔数组 <code>vis</code> 标记元素是否已被使用</li><li>使用深度优先搜索（DFS）进行回溯：<ul><li><strong>终止条件：</strong>当前路径长度等于数组长度，将路径拷贝加入结果集</li><li><strong>遍历选择：</strong>遍历数组中的每个元素：<ul><li>如果元素未被使用，则将其加入路径并标记为已使用</li><li>递归进入下一层决策树</li><li>回溯：移除路径最后一个元素并取消标记</li></ul></li></ul></li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">permute</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    List&lt;List&lt;Integer&gt;&gt; ans = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    List&lt;Integer&gt; t = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="type">boolean</span>[] vis = <span class="keyword">new</span> <span class="title class_">boolean</span>[nums.length];</span><br><span class="line">    dfs(nums, ans, t, vis);</span><br><span class="line">    <span class="keyword">return</span> ans;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">dfs</span><span class="params">(<span class="type">int</span>[] nums, List&lt;List&lt;Integer&gt;&gt; ans, List&lt;Integer&gt; t, <span class="type">boolean</span>[] vis)</span> &#123;</span><br><span class="line">    <span class="comment">// 当路径长度等于数组长度时，将当前路径加入结果集</span></span><br><span class="line">    <span class="keyword">if</span> (t.size() == nums.length) &#123;</span><br><span class="line">        <span class="comment">// 注意创建新列表</span></span><br><span class="line">        ans.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(t));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历所有元素</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums.length; i++) &#123;</span><br><span class="line">        <span class="comment">// 如果元素未被使用</span></span><br><span class="line">        <span class="keyword">if</span> (!vis[i]) &#123;</span><br><span class="line">            <span class="comment">// 做出选择</span></span><br><span class="line">            t.add(nums[i]);</span><br><span class="line">            vis[i] = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 递归进入下一层</span></span><br><span class="line">            dfs(nums, ans, t, vis);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 撤销选择（回溯）</span></span><br><span class="line">            t.removeLast();</span><br><span class="line">            vis[i] = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="关键步骤图示"><a href="#关键步骤图示" class="headerlink" title="关键步骤图示"></a>关键步骤图示</h2><p><strong>初始状态</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Level 0: </span><br><span class="line">  t = [] </span><br><span class="line">  vis = [false, false, false]</span><br></pre></td></tr></table></figure><p><strong>第一层递归（选择第一个元素）</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">选择1:</span><br><span class="line">  t = [1]</span><br><span class="line">  vis = [true, false, false]</span><br><span class="line">  └─ 进入第二层递归</span><br></pre></td></tr></table></figure><p><strong>第二层递归（选择第二个元素）</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">选择2:</span><br><span class="line">  t = [1, 2]</span><br><span class="line">  vis = [true, true, false]</span><br><span class="line">  └─ 进入第三层递归</span><br></pre></td></tr></table></figure><p><strong>第三层递归（选择第三个元素）</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">选择3:</span><br><span class="line">  t = [1, 2, 3] → 添加到结果集 ✅</span><br><span class="line">  vis = [true, true, true]</span><br><span class="line">  ├─ 回溯：移除3</span><br><span class="line">  │   t = [1, 2]</span><br><span class="line">  │   vis = [true, true, false]</span><br><span class="line">  └─ 回溯：移除2</span><br><span class="line">      t = [1]</span><br><span class="line">      vis = [true, false, false]</span><br></pre></td></tr></table></figure><p><strong>第二层递归的其他选择</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">选择3:</span><br><span class="line">  t = [1, 3]</span><br><span class="line">  vis = [true, false, true]</span><br><span class="line">  └─ 进入第三层递归</span><br><span class="line">      选择2:</span><br><span class="line">        t = [1, 3, 2] → 添加到结果集 ✅</span><br><span class="line">        vis = [true, true, true]</span><br><span class="line">        ├─ 回溯：移除2</span><br><span class="line">        │   t = [1, 3]</span><br><span class="line">        │   vis = [true, false, true]</span><br><span class="line">        └─ 回溯：移除3</span><br><span class="line">            t = [1]</span><br><span class="line">            vis = [true, false, false]</span><br></pre></td></tr></table></figure><p><strong>第一层递归的其他选择</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">选择2:</span><br><span class="line">  t = [2]</span><br><span class="line">  vis = [false, true, false]</span><br><span class="line">  └─ 进入第二层递归</span><br><span class="line">      选择1:</span><br><span class="line">        t = [2, 1]</span><br><span class="line">        vis = [true, true, false]</span><br><span class="line">        └─ 进入第三层递归</span><br><span class="line">            选择3:</span><br><span class="line">              t = [2, 1, 3] → 添加到结果集 ✅</span><br><span class="line">              vis = [true, true, true]</span><br><span class="line">      选择3:</span><br><span class="line">        t = [2, 3]</span><br><span class="line">        vis = [false, true, true]</span><br><span class="line">        └─ 进入第三层递归</span><br><span class="line">            选择1:</span><br><span class="line">              t = [2, 3, 1] → 添加到结果集 ✅</span><br><span class="line">              vis = [true, true, true]</span><br><span class="line"></span><br><span class="line">选择3:</span><br><span class="line">  t = [3]</span><br><span class="line">  vis = [false, false, true]</span><br><span class="line">  └─ 进入第二层递归</span><br><span class="line">      选择1:</span><br><span class="line">        t = [3, 1]</span><br><span class="line">        vis = [true, false, true]</span><br><span class="line">        └─ 进入第三层递归</span><br><span class="line">            选择2:</span><br><span class="line">              t = [3, 1, 2] → 添加到结果集 ✅</span><br><span class="line">              vis = [true, true, true]</span><br><span class="line">      选择2:</span><br><span class="line">        t = [3, 2]</span><br><span class="line">        vis = [false, true, true]</span><br><span class="line">        └─ 进入第三层递归</span><br><span class="line">            选择1:</span><br><span class="line">              t = [3, 2, 1] → 添加到结果集 ✅</span><br><span class="line">              vis = [true, true, true]</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n*n!)</code>，共有 <code>n!</code> 个排列，每个排列需要 <code>O(n)</code> 时间复制到结果列表中。</li><li><strong>空间复杂度：</strong><code>O(n)</code>，递归栈深度为 <code>n</code>，标记数组 <code>vis</code> 占用 <code>O(n)</code> 空间（结果空间不计入复杂度分析）。</li></ul><h1 id="关键点总结"><a href="#关键点总结" class="headerlink" title="关键点总结"></a>关键点总结</h1><ol><li><strong>回溯模板：</strong>遵循”选择-递归-撤销”的标准回溯框架</li><li><strong>路径拷贝：</strong><code>ans.add(new ArrayList&lt;&gt;(t))</code> 创建新列表避免引用问题</li><li><strong>状态标记：</strong>使用 <code>vis</code> 数组高效判断元素是否可用</li><li><strong>去重处理：</strong>题目已说明数组无重复数字，无需额外去重逻辑</li></ol><p>回溯法是解决排列组合问题的经典方法，通过深度优先搜索遍历所有可能性，配合状态标记保证每个元素只使用一次。掌握回溯算法的核心框架和剪枝技巧，能高效解决此类问题。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/permutations/description/" title="46. 全排列 | 力扣（LeetCode）">46. 全排列 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/permutations/solutions/9914/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/" title="46. 全排列 | 题解 | liweiwei1419">46. 全排列 | 题解 | liweiwei1419</a><br><a href="https://leetcode.cn/problems/permutations/solutions/2363882/46-quan-pai-lie-hui-su-qing-xi-tu-jie-by-6o7h/" title="46. 全排列 | 题解 | Krahets">46. 全排列 | 题解 | Krahets</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给定一个不含重复数字的数组 &lt;code&gt;nums&lt;/code&gt; ，返回其所有可能的全排列 。你可以按任意顺序返回答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [1, 2, 3]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [0, 1]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[0, 1], [1, 0]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;nums = [1]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[[1]]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= nums.length &amp;lt;= 6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-10 &amp;lt;= nums[i] &amp;lt;= 10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nums&lt;/code&gt; 中的所有整数互不相同&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="深度优先搜索" scheme="https://www.cylong.com/tags/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
    
    <category term="剪枝" scheme="https://www.cylong.com/tags/%E5%89%AA%E6%9E%9D/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
    <category term="回溯" scheme="https://www.cylong.com/tags/%E5%9B%9E%E6%BA%AF/"/>
    
  </entry>
  
  <entry>
    <title>二叉树原地展开为链表：简洁递归解法深入分析（LeetCode 114）</title>
    <link href="https://www.cylong.com/blog/2025/07/14/flatten-binary-tree-to-linked-list/"/>
    <id>https://www.cylong.com/blog/2025/07/14/flatten-binary-tree-to-linked-list/</id>
    <published>2025-07-14T14:26:46.000Z</published>
    <updated>2025-07-14T14:26:46.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>给你二叉树的根结点 <code>root</code> ，请你将它展开为一个单链表：</p><ul><li>展开后的单链表应该同样使用 <code>TreeNode</code> ，其中 <code>right</code> 子指针指向链表中下一个结点，而左子指针始终为 <code>null</code>。</li><li>展开后的单链表应该与二叉树 <strong>先序遍历</strong> 顺序相同。</li></ul><p><strong>示例 1:</strong></p><img src="/blog/2025/07/14/flatten-binary-tree-to-linked-list/flaten.jpg" class=""><blockquote><p><strong>输入：</strong><code>root = [1, 2, 5, 3, 4, null, 6]</code><br><strong>输出：</strong><code>[1, null, 2, null, 3, null, 4, null, 5, null, 6]</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>root = []</code><br><strong>输出：</strong><code>[]</code></p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>root = [0]</code><br><strong>输出：</strong><code>[0]</code></p></blockquote><p><strong>提示:</strong></p><ul><li>树中结点数在范围 <code>[0, 2000]</code> 内</li><li><code>-100 &lt;= Node.val &lt;= 100</code></li></ul><span id="more"></span><h1 id="递归后序遍历"><a href="#递归后序遍历" class="headerlink" title="递归后序遍历"></a>递归后序遍历</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>核心思路采用递归后序遍历：</p><ol><li>递归展开左右子树</li><li>将左子树插入根节点与右子树之间</li><li>遍历至新链表末端连接右子树</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">flatten</span><span class="params">(TreeNode root)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (root == <span class="literal">null</span>) <span class="keyword">return</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 递归展开左右子树</span></span><br><span class="line">    flatten(root.left);</span><br><span class="line">    flatten(root.right);</span><br><span class="line">    </span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">left</span> <span class="operator">=</span> root.left;   <span class="comment">// 保存左子树</span></span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">right</span> <span class="operator">=</span> root.right; <span class="comment">// 保存右子树</span></span><br><span class="line">    </span><br><span class="line">    root.left = <span class="literal">null</span>;            <span class="comment">// 切断左指针</span></span><br><span class="line">    root.right = left;           <span class="comment">// 左子树变为右子树</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 遍历至新链表末端</span></span><br><span class="line">    <span class="type">TreeNode</span> <span class="variable">cur</span> <span class="operator">=</span> root;</span><br><span class="line">    <span class="keyword">while</span> (cur.right != <span class="literal">null</span>) &#123;</span><br><span class="line">        cur = cur.right;</span><br><span class="line">    &#125;</span><br><span class="line">    cur.right = right;          <span class="comment">// 连接原右子树</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="算法流程详解"><a href="#算法流程详解" class="headerlink" title="算法流程详解"></a>算法流程详解</h2><ol><li><strong>递归终止：</strong>当前节点为 <code>null</code> 时返回</li><li><strong>后序遍历：</strong><ul><li>先递归处理左子树（<code>flatten(root.left)</code>）</li><li>再递归处理右子树（<code>flatten(root.right)</code>）</li></ul></li><li><strong>链表重组：</strong><ul><li>保存当前左右子树引用</li><li>将左子树作为新的右子树（<code>root.right = left</code>）</li><li>遍历新右子树找到末端节点</li><li>将原右子树接在末端节点后</li></ul></li></ol><h2 id="关键步骤图示"><a href="#关键步骤图示" class="headerlink" title="关键步骤图示"></a>关键步骤图示</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">原始结构：</span><br><span class="line">   root</span><br><span class="line">   /  \</span><br><span class="line">left  right</span><br><span class="line"></span><br><span class="line">重组后结构：</span><br><span class="line">root</span><br><span class="line">  \</span><br><span class="line">  left (展开的链表)</span><br><span class="line">     \</span><br><span class="line">    right (展开的链表)</span><br><span class="line"></span><br><span class="line">注：这里把 left 和 right 变为叶子节点，会更好理解</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(n)</code>，每个节点被访问两次，递归展开时访问一次，寻找链表末端时访问一次。</li><li><strong>空间复杂度：</strong><code>O(n)</code>，递归栈深度取决于树的高度，最坏情况（链状树）空间复杂度为 <code>O(n)</code>，平衡树情况为 <code>O(logn)</code>。</li></ul><h1 id="关键点总结"><a href="#关键点总结" class="headerlink" title="关键点总结"></a>关键点总结</h1><ol><li><strong>后序遍历顺序：</strong>必须保证左右子树都已展开成链表后才能进行根节点的重组。</li><li><strong>链表拼接细节：</strong>左子树插入后需遍历到末端再连接右子树，注意切断左指针避免结构混乱。</li><li><strong>原地修改：</strong>直接修改节点指针，不新建数据结构。</li></ol><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/description/" title="114. 二叉树展开为链表 | 力扣（LeetCode）">114. 二叉树展开为链表 | 力扣（LeetCode）</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;给你二叉树的根结点 &lt;code&gt;root&lt;/code&gt; ，请你将它展开为一个单链表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;展开后的单链表应该同样使用 &lt;code&gt;TreeNode&lt;/code&gt; ，其中 &lt;code&gt;right&lt;/code&gt; 子指针指向链表中下一个结点，而左子指针始终为 &lt;code&gt;null&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;展开后的单链表应该与二叉树 &lt;strong&gt;先序遍历&lt;/strong&gt; 顺序相同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;img src=&quot;/blog/2025/07/14/flatten-binary-tree-to-linked-list/flaten.jpg&quot; class=&quot;&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;root = [1, 2, 5, 3, 4, null, 6]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[1, null, 2, null, 3, null, 4, null, 5, null, 6]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;root = []&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;root = [0]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;[0]&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;树中结点数在范围 &lt;code&gt;[0, 2000]&lt;/code&gt; 内&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-100 &amp;lt;= Node.val &amp;lt;= 100&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="链表" scheme="https://www.cylong.com/tags/%E9%93%BE%E8%A1%A8/"/>
    
    <category term="树" scheme="https://www.cylong.com/tags/%E6%A0%91/"/>
    
    <category term="二叉树" scheme="https://www.cylong.com/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"/>
    
    <category term="深度优先搜索" scheme="https://www.cylong.com/tags/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>前缀树（Trie）实现：插入、搜索与前缀匹配操作（LeetCode 208）</title>
    <link href="https://www.cylong.com/blog/2025/07/10/implement-trie-prefix-tree/"/>
    <id>https://www.cylong.com/blog/2025/07/10/implement-trie-prefix-tree/</id>
    <published>2025-07-10T15:06:58.000Z</published>
    <updated>2025-07-10T15:06:58.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p><code>Trie</code>（发音类似 “try”）或者说 <strong>前缀树</strong> 是一种树形数据结构，用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景，例如自动补全和拼写检查。请你实现 <code>Trie</code> 类：</p><ul><li><code>Trie()</code> 初始化前缀树对象。</li><li><code>void insert(String word)</code> 向前缀树中插入字符串 <code>word</code> 。</li><li><code>boolean search(String word)</code> 如果字符串 <code>word</code> 在前缀树中，返回 <code>true</code>（即，在检索之前已经插入）；否则，返回 <code>false</code> 。</li><li><code>boolean startsWith(String prefix)</code> 如果之前已经插入的字符串 <code>word</code> 的前缀之一为 <code>prefix</code> ，返回 <code>true</code> ；否则，返回 <code>false</code> 。</li></ul><p><strong>示例 1:</strong></p><blockquote><p><strong>输入：</strong><br><code>[&quot;Trie&quot;, &quot;insert&quot;, &quot;search&quot;, &quot;search&quot;, &quot;startsWith&quot;, &quot;insert&quot;, &quot;search&quot;]</code><br><code>[[], [&quot;apple&quot;], [&quot;apple&quot;], [&quot;app&quot;], [&quot;app&quot;], [&quot;app&quot;], [&quot;app&quot;]]</code><br><strong>输出：</strong><br><code>[null, null, true, false, true, null, true]</code><br><strong>解释：</strong><br><code>Trie trie = new Trie();</code><br><code>trie.insert(&quot;apple&quot;);</code><br><code>trie.search(&quot;apple&quot;);</code>   // 返回 True<br><code>trie.search(&quot;app&quot;);</code>     // 返回 False<br><code>trie.startsWith(&quot;app&quot;);</code> // 返回 True<br><code>trie.insert(&quot;app&quot;);</code><br><code>trie.search(&quot;app&quot;);</code>     // 返回 True</p></blockquote><p><strong>提示:</strong></p><ul><li><code>1 &lt;= word.length, prefix.length &lt;= 2000</code></li><li><code>word</code> 和 <code>prefix</code> 仅由小写英文字母组成</li><li><code>insert</code>、<code>search</code> 和 <code>startsWith</code> 调用次数总计不超过 <code>3 * 10^4</code> 次</li></ul><span id="more"></span><h1 id="基于数组的-Trie-实现"><a href="#基于数组的-Trie-实现" class="headerlink" title="基于数组的 Trie 实现"></a>基于数组的 Trie 实现</h1><p>前缀树（<code>Trie</code>）是一种高效检索字符串数据集中的键的树形数据结构。它广泛应用于搜索引擎、拼写检查、自动补全等场景。<code>Trie</code> 的核心思想是利用字符串的公共前缀来减少查询时间，最大限度地减少无谓的字符串比较。</p><h2 id="Trie-节点设计"><a href="#Trie-节点设计" class="headerlink" title="Trie 节点设计"></a>Trie 节点设计</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Node</span> &#123;</span><br><span class="line">    <span class="comment">// 子节点数组，每个位置对应一个字母（a-z）</span></span><br><span class="line">    Node[] son = <span class="keyword">new</span> <span class="title class_">Node</span>[<span class="number">26</span>];</span><br><span class="line">    <span class="comment">// 标记当前节点是否为单词结尾</span></span><br><span class="line">    <span class="type">boolean</span> end;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Trie-类实现"><a href="#Trie-类实现" class="headerlink" title="Trie 类实现"></a>Trie 类实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Trie</span> &#123;</span><br><span class="line">    <span class="comment">// 根节点，不存储实际字符</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Node</span> <span class="variable">root</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Node</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Trie</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 向 Trie 中插入一个单词</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> word 要插入的单词</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(String word)</span> &#123;</span><br><span class="line">        <span class="comment">// 从根节点开始</span></span><br><span class="line">        <span class="type">Node</span> <span class="variable">cur</span> <span class="operator">=</span> root;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> c : word.toCharArray()) &#123;</span><br><span class="line">            <span class="comment">// 将字符转换为数组索引（0-25）</span></span><br><span class="line">            c -= <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">            <span class="comment">// 如果当前字符对应的路径不存在</span></span><br><span class="line">            <span class="keyword">if</span> (cur.son[c] == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 创建新节点</span></span><br><span class="line">                cur.son[c] = <span class="keyword">new</span> <span class="title class_">Node</span>();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 移动到子节点</span></span><br><span class="line">            cur = cur.son[c];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 标记单词结束</span></span><br><span class="line">        cur.end = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 搜索 Trie 中是否存在完整单词</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> word 要搜索的单词</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 存在返回 true，否则返回 false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">search</span><span class="params">(String word)</span> &#123;</span><br><span class="line">        <span class="comment">// 需要完全匹配（状态值 2）</span></span><br><span class="line">        <span class="keyword">return</span> find(word) == <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 检查 Trie 中是否有以指定前缀开头的单词</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> prefix 要检查的前缀</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 存在返回 true，否则返回 false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">startsWith</span><span class="params">(String prefix)</span> &#123;</span><br><span class="line">        <span class="comment">// 只要路径存在即可（状态值 1 或 2）</span></span><br><span class="line">        <span class="keyword">return</span> find(prefix) != <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 内部查找方法，返回匹配状态</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> word 要查找的单词或前缀</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 0: 未找到, 1: 前缀存在, 2: 完整单词存在</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">find</span><span class="params">(String word)</span> &#123;</span><br><span class="line">        <span class="type">Node</span> <span class="variable">cur</span> <span class="operator">=</span> root;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span> c : word.toCharArray()) &#123;</span><br><span class="line">            <span class="comment">// 字符转索引</span></span><br><span class="line">            c -= <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">            <span class="keyword">if</span> (cur.son[c] == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 路径中断，未找到</span></span><br><span class="line">                <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 继续向下查找</span></span><br><span class="line">            cur = cur.son[c];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 路径存在，检查是否为完整单词</span></span><br><span class="line">        <span class="keyword">return</span> cur.end ? <span class="number">2</span> : <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><ul><li>插入操作：<code>O(L)</code>，其中 <code>L</code> 是插入单词的长度。需要遍历单词的每个字符。</li><li>搜索操作：<code>O(L)</code>，其中 <code>L</code> 是搜索单词的长度。需要遍历单词的每个字符。</li><li>前缀搜索：<code>O(L)</code>，其中 <code>L</code> 是前缀的长度。需要遍历前缀的每个字符。</li></ul></li><li><strong>空间复杂度：</strong><ul><li>最坏情况：<code>O(MN)</code>，其中 <code>M</code> 是单词的平均长度，<code>N</code> 是插入的单词数量。每个字符都需要一个节点。</li><li>最佳情况：当单词共享大量前缀时，空间复杂度会显著降低。</li></ul></li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ol><li><strong>节点结构：</strong>每个节点包含一个长度为 <code>26</code> 的子节点数组（对应 <code>26</code> 个小写字母）和一个结束标志。</li><li><strong>路径创建：</strong>在插入过程中，如果路径不存在则动态创建新节点。</li><li><strong>结束标志：</strong>单词插入完成后，在最后一个节点标记结束标志。</li><li><strong>查找优化：</strong>使用统一的 <code>find</code> 方法处理完整单词查找和前缀查找，避免代码重复。</li><li><strong>字符转换：</strong>通过 <code>c - &#39;a&#39;</code> 将字符转换为数组索引（0-25），高效访问子节点。</li></ol><p><code>Trie</code> 是一种高效处理字符串相关问题的数据结构，特别适合前缀匹配的场景。本文实现的 <code>Trie</code> 结构清晰，操作高效，时间复杂度均为线性级别。理解 <code>Trie</code> 的工作原理对于解决字符串搜索、自动补全等问题至关重要。实际应用中，可以根据需求扩展 <code>Trie</code> 功能，如添加词频统计、删除操作等。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/implement-trie-prefix-tree/description/" title="208. 实现 Trie (前缀树) | 力扣（LeetCode）">208. 实现 Trie (前缀树) | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/implement-trie-prefix-tree/solutions/2993894/cong-er-cha-shu-dao-er-shi-liu-cha-shu-p-xsj4/" title="208. 实现 Trie (前缀树) | 题解 | 灵茶山艾府">208. 实现 Trie (前缀树) | 题解 | 灵茶山艾府</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;&lt;code&gt;Trie&lt;/code&gt;（发音类似 “try”）或者说 &lt;strong&gt;前缀树&lt;/strong&gt; 是一种树形数据结构，用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景，例如自动补全和拼写检查。请你实现 &lt;code&gt;Trie&lt;/code&gt; 类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Trie()&lt;/code&gt; 初始化前缀树对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;void insert(String word)&lt;/code&gt; 向前缀树中插入字符串 &lt;code&gt;word&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boolean search(String word)&lt;/code&gt; 如果字符串 &lt;code&gt;word&lt;/code&gt; 在前缀树中，返回 &lt;code&gt;true&lt;/code&gt;（即，在检索之前已经插入）；否则，返回 &lt;code&gt;false&lt;/code&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boolean startsWith(String prefix)&lt;/code&gt; 如果之前已经插入的字符串 &lt;code&gt;word&lt;/code&gt; 的前缀之一为 &lt;code&gt;prefix&lt;/code&gt; ，返回 &lt;code&gt;true&lt;/code&gt; ；否则，返回 &lt;code&gt;false&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;br&gt;&lt;code&gt;[&amp;quot;Trie&amp;quot;, &amp;quot;insert&amp;quot;, &amp;quot;search&amp;quot;, &amp;quot;search&amp;quot;, &amp;quot;startsWith&amp;quot;, &amp;quot;insert&amp;quot;, &amp;quot;search&amp;quot;]&lt;/code&gt;&lt;br&gt;&lt;code&gt;[[], [&amp;quot;apple&amp;quot;], [&amp;quot;apple&amp;quot;], [&amp;quot;app&amp;quot;], [&amp;quot;app&amp;quot;], [&amp;quot;app&amp;quot;], [&amp;quot;app&amp;quot;]]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;br&gt;&lt;code&gt;[null, null, true, false, true, null, true]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;&lt;br&gt;&lt;code&gt;Trie trie = new Trie();&lt;/code&gt;&lt;br&gt;&lt;code&gt;trie.insert(&amp;quot;apple&amp;quot;);&lt;/code&gt;&lt;br&gt;&lt;code&gt;trie.search(&amp;quot;apple&amp;quot;);&lt;/code&gt;   // 返回 True&lt;br&gt;&lt;code&gt;trie.search(&amp;quot;app&amp;quot;);&lt;/code&gt;     // 返回 False&lt;br&gt;&lt;code&gt;trie.startsWith(&amp;quot;app&amp;quot;);&lt;/code&gt; // 返回 True&lt;br&gt;&lt;code&gt;trie.insert(&amp;quot;app&amp;quot;);&lt;/code&gt;&lt;br&gt;&lt;code&gt;trie.search(&amp;quot;app&amp;quot;);&lt;/code&gt;     // 返回 True&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= word.length, prefix.length &amp;lt;= 2000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;word&lt;/code&gt; 和 &lt;code&gt;prefix&lt;/code&gt; 仅由小写英文字母组成&lt;/li&gt;
&lt;li&gt;&lt;code&gt;insert&lt;/code&gt;、&lt;code&gt;search&lt;/code&gt; 和 &lt;code&gt;startsWith&lt;/code&gt; 调用次数总计不超过 &lt;code&gt;3 * 10^4&lt;/code&gt; 次&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="字符串" scheme="https://www.cylong.com/tags/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
    
    <category term="树" scheme="https://www.cylong.com/tags/%E6%A0%91/"/>
    
    <category term="字典树" scheme="https://www.cylong.com/tags/%E5%AD%97%E5%85%B8%E6%A0%91/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
  <entry>
    <title>多源 BFS 解决腐烂橘子问题（LeetCode 994）</title>
    <link href="https://www.cylong.com/blog/2025/07/09/rotting-oranges/"/>
    <id>https://www.cylong.com/blog/2025/07/09/rotting-oranges/</id>
    <published>2025-07-09T13:44:07.000Z</published>
    <updated>2025-07-09T13:44:07.000Z</updated>
    
    <content type="html"><![CDATA[<hr><h1 id="题目描述"><a href="#题目描述" class="headerlink" title="题目描述"></a>题目描述</h1><p>在给定的 <code>m x n</code> 网格 <code>grid</code> 中，每个单元格可以有以下三个值之一：</p><ul><li>值 <code>0</code> 代表空单元格；</li><li>值 <code>1</code> 代表新鲜橘子；</li><li>值 <code>2</code> 代表腐烂的橘子。</li></ul><p>每分钟，腐烂的橘子 周围 <code>4</code> 个方向上相邻 的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能，返回 <code>-1</code> 。</p><p><strong>示例 1:</strong></p><img src="/blog/2025/07/09/rotting-oranges/oranges.png" class=""><blockquote><p><strong>输入：</strong><code>grid = [[2, 1, 1],[1, 1, 0],[0, 1, 1]]</code><br><strong>输出：</strong><code>4</code></p></blockquote><p><strong>示例 2:</strong></p><blockquote><p><strong>输入：</strong><code>grid = [[2, 1, 1],[0, 1, 1],[1, 0, 1]]</code><br><strong>输出：</strong><code>-1</code><br><strong>解释：</strong>左下角的橘子（第 <code>2</code> 行， 第 <code>0</code> 列）永远不会腐烂，因为腐烂只会发生在 <code>4</code> 个方向上。</p></blockquote><p><strong>示例 3:</strong></p><blockquote><p><strong>输入：</strong><code>grid = [[0, 2]]</code><br><strong>输出：</strong><code>0</code><br><strong>解释：</strong>因为 <code>0</code> 分钟时已经没有新鲜橘子了，所以答案就是 <code>0</code> 。</p></blockquote><p><strong>提示:</strong></p><ul><li><code>m == grid.length</code></li><li><code>n == grid[i].length</code></li><li><code>1 &lt;= m, n &lt;= 10</code></li><li><code>grid[i][j]</code> 仅为 <code>0</code>、<code>1</code> 或 <code>2</code></li></ul><span id="more"></span><h1 id="多源-BFS（广度优先搜索）"><a href="#多源-BFS（广度优先搜索）" class="headerlink" title="多源 BFS（广度优先搜索）"></a>多源 BFS（广度优先搜索）</h1><h2 id="核心思路"><a href="#核心思路" class="headerlink" title="核心思路"></a>核心思路</h2><p>使用多源 <code>BFS</code> 模拟橘子腐烂的过程：</p><ol><li><strong>初始化：</strong>遍历整个网格，统计新鲜橘子的数量，并将所有腐烂橘子的坐标加入队列。</li><li><strong>BFS遍历：</strong>从所有腐烂橘子同时开始扩散。<ul><li>每一轮处理当前队列中的所有腐烂橘子（同一分钟）。</li><li>每个腐烂橘子使其上下左右相邻的新鲜橘子腐烂。</li><li>新腐烂的橘子加入队列（下一分钟继续扩散）。</li></ul></li><li><strong>结果判断：</strong><code>BFS</code> 结束后，若还有新鲜橘子剩余，返回 <code>-1</code>；否则返回分钟数。</li></ol><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">orangesRotting</span><span class="params">(<span class="type">int</span>[][] grid)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">rows</span> <span class="operator">=</span> grid.length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">cols</span> <span class="operator">=</span> grid[<span class="number">0</span>].length;</span><br><span class="line">        <span class="type">int</span> <span class="variable">minutes</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// 记录新鲜橘子数量</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">freshCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// BFS 队列</span></span><br><span class="line">        Queue&lt;<span class="type">int</span>[]&gt; queue = <span class="keyword">new</span> <span class="title class_">ArrayDeque</span>&lt;&gt;();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 四个方向：上、下、左、右</span></span><br><span class="line">        <span class="type">int</span>[][] directions = &#123;&#123;-<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">1</span>, <span class="number">0</span>&#125;, &#123;<span class="number">0</span>, -<span class="number">1</span>&#125;, &#123;<span class="number">0</span>, <span class="number">1</span>&#125;&#125;;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 初始化：统计新鲜橘子，并将腐烂橘子入队</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rows; i++) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; cols; j++) &#123;</span><br><span class="line">                <span class="keyword">if</span> (grid[i][j] == <span class="number">1</span>) &#123;</span><br><span class="line">                    freshCount++;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (grid[i][j] == <span class="number">2</span>) &#123;</span><br><span class="line">                    queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;i, j&#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// BFS 开始：当还有新鲜橘子且队列不为空时</span></span><br><span class="line">        <span class="keyword">while</span> (freshCount &gt; <span class="number">0</span> &amp;&amp; !queue.isEmpty()) &#123;</span><br><span class="line">            <span class="comment">// 当前层的橘子数量</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">levelSize</span> <span class="operator">=</span> queue.size();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 处理当前层的所有腐烂橘子</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; levelSize; i++) &#123;</span><br><span class="line">                <span class="type">int</span>[] cur = queue.poll();</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 向四个方向扩散</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span>[] dir : directions) &#123;</span><br><span class="line">                    <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> cur[<span class="number">0</span>] + dir[<span class="number">0</span>];</span><br><span class="line">                    <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> cur[<span class="number">1</span>] + dir[<span class="number">1</span>];</span><br><span class="line">                    </span><br><span class="line">                    <span class="comment">// 检查边界且是否为新鲜橘子</span></span><br><span class="line">                    <span class="keyword">if</span> (x &gt;= <span class="number">0</span> &amp;&amp; x &lt; rows &amp;&amp; y &gt;= <span class="number">0</span> &amp;&amp; y &lt; cols &amp;&amp; grid[x][y] == <span class="number">1</span>) &#123;</span><br><span class="line">                        <span class="comment">// 标记为腐烂</span></span><br><span class="line">                        grid[x][y] = <span class="number">2</span>;</span><br><span class="line">                        <span class="comment">// 新鲜橘子减少</span></span><br><span class="line">                        freshCount--;</span><br><span class="line">                        <span class="comment">// 新腐烂橘子入队</span></span><br><span class="line">                        queue.offer(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;x, y&#125;);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 当前层处理完毕，分钟数增加</span></span><br><span class="line">            minutes++;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 若还有剩余新鲜橘子，返回 -1；否则返回分钟数</span></span><br><span class="line">        <span class="keyword">return</span> freshCount == <span class="number">0</span> ? minutes : -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="复杂度分析"><a href="#复杂度分析" class="headerlink" title="复杂度分析"></a>复杂度分析</h2><ul><li><strong>时间复杂度：</strong><code>O(mn)</code>，每个网格单元最多被访问一次。</li><li><strong>空间复杂度：</strong><code>O(mn)</code>，最坏情况下队列需要存储所有腐烂橘子。</li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ol><li><strong>多源BFS：</strong>所有初始腐烂橘子同时开始扩散，确保分钟计数准确。</li><li><strong>层级处理：</strong>通过记录队列大小处理同一分钟的所有腐烂橘子。</li><li><strong>边界判断：</strong>扩散时检查网格边界和橘子状态。</li><li><strong>提前终止：</strong>当新鲜橘子数为 <code>0</code> 时，可提前结束 <code>BFS</code>。</li></ol><p>该解法高效地模拟了橘子腐烂的过程，时间复杂度与网格大小成正比，是最优解法。</p><h1 id="来源"><a href="#来源" class="headerlink" title="来源"></a>来源</h1><blockquote><p><a href="https://leetcode.cn/problems/rotting-oranges/description/" title="994. 腐烂的橘子 | 力扣（LeetCode）">994. 腐烂的橘子 | 力扣（LeetCode）</a><br><a href="https://leetcode.cn/problems/rotting-oranges/solutions/3712941/bfszhu-shi-fei-chang-qing-xi-ban-ben-by-jzug4/" title="994. 腐烂的橘子 | 题解 | Bravo">994. 腐烂的橘子 | 题解 | Krahets</a><br><a href="https://leetcode.cn/problems/rotting-oranges/solutions/2773461/duo-yuan-bfsfu-ti-dan-pythonjavacgojsrus-yfmh/" title="994. 腐烂的橘子 | 题解 | 灵茶山艾府">994. 腐烂的橘子 | 题解 | 灵茶山艾府</a></p></blockquote><hr>]]></content>
    
    
    <summary type="html">&lt;hr&gt;
&lt;h1 id=&quot;题目描述&quot;&gt;&lt;a href=&quot;#题目描述&quot; class=&quot;headerlink&quot; title=&quot;题目描述&quot;&gt;&lt;/a&gt;题目描述&lt;/h1&gt;&lt;p&gt;在给定的 &lt;code&gt;m x n&lt;/code&gt; 网格 &lt;code&gt;grid&lt;/code&gt; 中，每个单元格可以有以下三个值之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;值 &lt;code&gt;0&lt;/code&gt; 代表空单元格；&lt;/li&gt;
&lt;li&gt;值 &lt;code&gt;1&lt;/code&gt; 代表新鲜橘子；&lt;/li&gt;
&lt;li&gt;值 &lt;code&gt;2&lt;/code&gt; 代表腐烂的橘子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每分钟，腐烂的橘子 周围 &lt;code&gt;4&lt;/code&gt; 个方向上相邻 的新鲜橘子都会腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能，返回 &lt;code&gt;-1&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;img src=&quot;/blog/2025/07/09/rotting-oranges/oranges.png&quot; class=&quot;&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;grid = [[2, 1, 1],[1, 1, 0],[0, 1, 1]]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;4&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;grid = [[2, 1, 1],[0, 1, 1],[1, 0, 1]]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;-1&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;左下角的橘子（第 &lt;code&gt;2&lt;/code&gt; 行， 第 &lt;code&gt;0&lt;/code&gt; 列）永远不会腐烂，因为腐烂只会发生在 &lt;code&gt;4&lt;/code&gt; 个方向上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;&lt;code&gt;grid = [[0, 2]]&lt;/code&gt;&lt;br&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;code&gt;0&lt;/code&gt;&lt;br&gt;&lt;strong&gt;解释：&lt;/strong&gt;因为 &lt;code&gt;0&lt;/code&gt; 分钟时已经没有新鲜橘子了，所以答案就是 &lt;code&gt;0&lt;/code&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;m == grid.length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n == grid[i].length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= m, n &amp;lt;= 10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid[i][j]&lt;/code&gt; 仅为 &lt;code&gt;0&lt;/code&gt;、&lt;code&gt;1&lt;/code&gt; 或 &lt;code&gt;2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="LeetCode" scheme="https://www.cylong.com/categories/LeetCode/"/>
    
    
    <category term="Java" scheme="https://www.cylong.com/tags/Java/"/>
    
    <category term="LeetCode" scheme="https://www.cylong.com/tags/LeetCode/"/>
    
    <category term="学习笔记" scheme="https://www.cylong.com/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="数组" scheme="https://www.cylong.com/tags/%E6%95%B0%E7%BB%84/"/>
    
    <category term="队列" scheme="https://www.cylong.com/tags/%E9%98%9F%E5%88%97/"/>
    
    <category term="矩阵" scheme="https://www.cylong.com/tags/%E7%9F%A9%E9%98%B5/"/>
    
    <category term="广度优先搜索" scheme="https://www.cylong.com/tags/%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/"/>
    
    <category term="LeetCode中等" scheme="https://www.cylong.com/tags/LeetCode%E4%B8%AD%E7%AD%89/"/>
    
  </entry>
  
</feed>
