<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Algorithms on Kmoon Blog</title>
    <link>https://blog.kmoon.fun/blog/algorithms/</link>
    <description>Recent content in Algorithms on Kmoon Blog</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    
      <managingEditor>hushan@kmoon.fun (Kmoon)</managingEditor>
    
    
      <webMaster>hushan@kmoon.fun (Kmoon)</webMaster>
    
    
    <copyright>Copyright © 2020-2026. Kmoon.</copyright>
    
    
    <lastBuildDate>Sun, 10 Nov 2024 21:47:16 +0800</lastBuildDate>
    
    
    <follow_challenge>
        <feedId>00000000000000000</feedId>
        <userId>00000000000000000</userId>
    </follow_challenge>
    
    <atom:link href="https://blog.kmoon.fun/blog/algorithms/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>【算法合集】：动态规划</title>
      <link>https://blog.kmoon.fun/%E7%AE%97%E6%B3%95%E5%90%88%E9%9B%86%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/</link>
      <pubDate>Sun, 10 Nov 2024 21:47:16 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/%E7%AE%97%E6%B3%95%E5%90%88%E9%9B%86%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/</guid>
      <description>&lt;h1 id=&#34;动态规划&#34;&gt;动态规划&lt;/h1&gt;&#xA;&lt;p&gt;首先确定有哪些状态，初始化 dp 数组，初始化 dp 数组的时候需要考虑纬度、状态。&lt;/p&gt;&#xA;&lt;p&gt;当前状态来源于前面的状态，写状态转移方程的时候，思考 &lt;strong&gt;= 号左边的状态在右边有哪些来源（依赖前面哪些状态）&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;线性-dp&#34;&gt;线性 DP&lt;/h2&gt;&#xA;&lt;p&gt;最基础的一维 DP，dp [i] 只依赖前面 dp [i-1] / dp [i-2]&lt;/p&gt;&#xA;&lt;p&gt;状态：dp[i] = 前 i 位置最优解&lt;/p&gt;&#xA;&lt;p&gt;经典题：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;打家劫舍 Ⅰ、Ⅱ&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;偷当前房子依赖于偷没偷左边的房子，如果偷了左边的就不能偷当前的,保持原来的状态：dp[i-1]，如果没偷左边的当前房子就可以偷：dp[i-2] + nums[i]，两者取最大值即可。&lt;/p&gt;&#xA;&lt;p&gt;打家劫舍 Ⅱ 数据结构变成了环形数组，首尾相互挨着，只能选择偷一个，所以我们分别去掉首尾计算一次，取最大值即可。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;爬楼梯、最小花费爬楼梯&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;爬楼梯这里考虑爬上当前台阶来源于两种方法：1️⃣ 从前两个台阶爬上来 2️⃣ 从前一个台阶爬上来，所以很容易推出状态转移方程：&lt;code&gt;dp[i] = dp[i-2] + dp[i-1]&lt;/code&gt;，这里可以看出爬楼梯本质上就是斐波那契数列&lt;/li&gt;&#xA;&lt;li&gt;最小花费爬楼梯加了一个新条件，目标从“计数”变成“求最值”:&lt;code&gt;dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])&lt;/code&gt;，需要考虑成本取最小值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;斐波那契数、杨辉三角&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;斐波那契数题目已经给出了状态转移方程：dp[n] = dp[n-1] + dp[n-2]&lt;/li&gt;&#xA;&lt;li&gt;杨辉三角同样也是给出了状态转移过程：每个数来源于左上方 + 右上方的值，即上一行的两个值，需要一行一行算出当前行的值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;4&#34;&gt;&#xA;&lt;li&gt;比特位计数&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;需要统计二进制表示中 1 的个数，这里我们知道二进制是逐个进位的，后面的数来源于前面的数(i 的状态依赖于 i 整除 2 的状态)。比如 3（11）的二进制中 1 的个数 = 1（01）的二进制中 1 的个数 + 3（11）原本末尾一位 1 的个数。&lt;/p&gt;&#xA;&lt;p&gt;总结来看就是：dp[i] = dp[i // 2] + (i &amp;amp; 1)，这里整除 2 其实也就是右移一位（dp[i] &amp;raquo; 1），(i &amp;amp; 1) 就是看 i 末尾是不是 “1”。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;区间-dp&#34;&gt;区间 DP&lt;/h2&gt;&#xA;&lt;p&gt;左右区间 i ~ j&lt;/p&gt;&#xA;&lt;p&gt;dp [i][j]：区间 [i,j] 的答案，从小区间推大区间&lt;/p&gt;&#xA;&lt;p&gt;枚举区间长度 → 枚举起点 i，终点 j=i+len-1&lt;/p&gt;&#xA;&lt;p&gt;经典题：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;最大子数组和&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;从小区间推大区间，数组里面有负数，要么拼到后面，要么重新开始，两者取最大值：dp[i] = max(dp[i - 1] + nums[i], nums[i])，返回的时候取最大：return max(dp)&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;最长递增子序列&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;外层枚举每一个位置 i（作为子序列的结尾），内层枚举 i 前面所有位置 j（尝试将 i 接在 j 后面），能递增：nums[j] &amp;lt; nums[i]，说明可以把 nums[i] 接在以 j 结尾的序列后面&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;乘积最大子数组&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;这道题同样存在负数，计算乘法可能会对结果产生反转。需要维护两个dp数组，一个乘积最大值，一个乘积最小值。乘积最大值的3个来源：拼在后面、重新开始、最小值拼，乘积最小值的3个来源：拼在后面、重新开始、最大值拼，最后结果肯定是返回乘积最大值数组的最大值&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;4&#34;&gt;&#xA;&lt;li&gt;回文子串、最长回文子串&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;因为要判断回文子串，需要先枚举子串长度，再枚举当前长度下所有可能的起始位置，状态转移就是只要首尾两个字符相等就可以更新，因为题目求总数，所以需要统计一下&lt;/p&gt;&#xA;&lt;p&gt;最长回文子串求的是最长子串，只需要维护一个最长长度即可&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;坐标网格棋盘-dp&#34;&gt;坐标/网格/棋盘 DP&lt;/h2&gt;&#xA;&lt;p&gt;从左上走到右下，只能右 / 下；dp [i][j] 由上边 / 左边转移&lt;/p&gt;&#xA;&lt;p&gt;经典题：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;不同路径 Ⅰ、Ⅱ&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;最小路径和&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;3&#34;&gt;&#xA;&lt;li&gt;最大正方形&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;模板：dp[i][j]=dp[i-1][j]+dp[i][j-1] / min(&amp;hellip;)&lt;/p&gt;&#xA;&lt;h2 id=&#34;二维-dp--双串-dp&#34;&gt;二维 DP / 双串 DP&lt;/h2&gt;&#xA;&lt;h3 id=&#34;二维-dp&#34;&gt;二维 DP&lt;/h3&gt;&#xA;&lt;p&gt;二维 DP 是指存在另一个纬度的状态，比如买卖股票场景下就存在持股、不持股两种状态。&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;买卖股票的最佳时机、买卖股票的最佳时机含冷冻期&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;双串&#34;&gt;双串&lt;/h3&gt;&#xA;&lt;p&gt;双串 DP 是指需要在两个数据结构上（数组、字符串）考虑。&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;最长公共子序列&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;编辑距离&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;树形-dp&#34;&gt;树形 DP&lt;/h2&gt;&#xA;&lt;p&gt;DFS + DP&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;打家劫舍 Ⅲ&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;不同的二叉搜索树&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;背包问题&#34;&gt;背包问题&lt;/h2&gt;&#xA;&lt;p&gt;背包问题需要理清 3 个核心顺序：&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-先物品-还是-先容量&#34;&gt;1. 先物品 还是 先容量&lt;/h3&gt;&#xA;&lt;p&gt;只需要分析题目是“&lt;strong&gt;组合&lt;/strong&gt;”还是“&lt;strong&gt;排列&lt;/strong&gt;”场景即可&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;组合：先物品后容量&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因为组合问题不考虑顺序，1+2 和 2+1 算同一种。&lt;/p&gt;&#xA;&lt;p&gt;先物品 = 控制物品只能按固定顺序出现，不会回头选，不产生新顺序 → 组合&lt;/p&gt;&#xA;&lt;p&gt;外层物品 = 单向遍历，不回头，物品只能按顺序出现，后面的物品不能再和前面的组合 → 无顺序 = 组合&lt;/p&gt;&#xA;&lt;p&gt;例如：零钱兑换、零钱兑换 II、完全平方数、分割等和子集、目标和&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;物品&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;物品&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;容量&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;物品&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;总容量&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;排列：先容量，后物品&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因为排列问题中顺序不同，算不同的答案。1+2 和 2+1 算两种&lt;/p&gt;&#xA;&lt;p&gt;先容量 = 每个位置都能选所有物品，会回头选，产生新顺序 → 排列&lt;/p&gt;&#xA;&lt;p&gt;外层容量 = 每个位置都能选全部物品，每个容量位置，都可以重新选所有物品 → 有顺序 = 排列&lt;/p&gt;&#xA;&lt;p&gt;例如：单词拆分、组合总和 IV&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;容量&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;总容量&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;物品&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;物品&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-容量正序-还是-倒序&#34;&gt;2. 容量正序 还是 倒序&lt;/h3&gt;&#xA;&lt;p&gt;决定是 01 背包 还是 完全背包&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;01 背包（物品只能用 1 次）：倒序遍历&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;每个物品只能用一次，所以不能让后面的更新用到前面刚更新的值 → 必须倒序&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;完全背包（物品可重复用）：正序遍历&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;物品可以用无限次，希望后面能用到前面刚更新的值 → 必须正序&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-初始化顺序求最小--求方案数--求可达&#34;&gt;3. 初始化顺序：求最小 / 求方案数 / 求可达&lt;/h3&gt;&#xA;&lt;p&gt;决定 dp 数组怎么初始化&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;① 求最小数&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;dp[0] = 0，其余 = 无穷大&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;② 求方案数&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;dp[0] = 1，其余 = 0&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;③ 求可达（True/False）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;dp[0] = True，其余 = False&lt;/p&gt;&#xA;&lt;h3 id=&#34;01-背包&#34;&gt;01 背包&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;分割等和子集&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;目标和&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;完全背包&#34;&gt;完全背包&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;完全平方数&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;零钱兑换&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;单词拆分&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;</description>
    </item>
    <item>
      <title>【算法合集】：回溯</title>
      <link>https://blog.kmoon.fun/%E7%AE%97%E6%B3%95%E5%90%88%E9%9B%86%E5%9B%9E%E6%BA%AF/</link>
      <pubDate>Sat, 09 Nov 2024 16:49:52 +0800</pubDate><author>hushan@kmoon.fun (Kmoon)</author>
      <guid>https://blog.kmoon.fun/%E7%AE%97%E6%B3%95%E5%90%88%E9%9B%86%E5%9B%9E%E6%BA%AF/</guid>
      <description>&lt;h2 id=&#34;组合型&#34;&gt;组合型&lt;/h2&gt;&#xA;&lt;h2 id=&#34;子集型&#34;&gt;子集型&lt;/h2&gt;&#xA;&lt;h2 id=&#34;排列型&#34;&gt;排列型&lt;/h2&gt;&#xA;&lt;h2 id=&#34;搜索型&#34;&gt;搜索型&lt;/h2&gt;&#xA;</description>
    </item>
  </channel>
</rss>
