This commit is contained in:
krahets
2023-07-26 08:59:03 +08:00
parent fd34c845bc
commit 974fea7de4
48 changed files with 299 additions and 299 deletions

View File

@@ -3713,8 +3713,8 @@ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]
<p>不难发现,此问题已不满足无后效性,状态转移方程 <span class="arithmatex">\(dp[i] = dp[i-1] + dp[i-2]\)</span> 也失效了,因为 <span class="arithmatex">\(dp[i-1]\)</span> 代表本轮跳 <span class="arithmatex">\(1\)</span> 阶,但其中包含了许多“上一轮跳 <span class="arithmatex">\(1\)</span> 阶上来的”方案,而为了满足约束,我们就不能将 <span class="arithmatex">\(dp[i-1]\)</span> 直接计入 <span class="arithmatex">\(dp[i]\)</span> 中。</p>
<p>为此,我们需要扩展状态定义:<strong>状态 <span class="arithmatex">\([i, j]\)</span> 表示处在第 <span class="arithmatex">\(i\)</span> 阶、并且上一轮跳了 <span class="arithmatex">\(j\)</span></strong>,其中 <span class="arithmatex">\(j \in \{1, 2\}\)</span> 。此状态定义有效地区分了上一轮跳了 <span class="arithmatex">\(1\)</span> 阶还是 <span class="arithmatex">\(2\)</span> 阶,我们可以据此来决定下一步该怎么跳:</p>
<ul>
<li><span class="arithmatex">\(j\)</span> 等于 <span class="arithmatex">\(1\)</span> ,即上一轮跳了 <span class="arithmatex">\(1\)</span> 阶时,这一轮只能选择跳 <span class="arithmatex">\(2\)</span></li>
<li><span class="arithmatex">\(j\)</span> 等于 <span class="arithmatex">\(2\)</span> ,即上一轮跳了 <span class="arithmatex">\(2\)</span> 阶时,这一轮可选择跳 <span class="arithmatex">\(1\)</span> 阶或跳 <span class="arithmatex">\(2\)</span></li>
<li><span class="arithmatex">\(j\)</span> 等于 <span class="arithmatex">\(1\)</span> ,即上一轮跳了 <span class="arithmatex">\(1\)</span> 阶时,这一轮只能选择跳 <span class="arithmatex">\(2\)</span></li>
<li><span class="arithmatex">\(j\)</span> 等于 <span class="arithmatex">\(2\)</span> ,即上一轮跳了 <span class="arithmatex">\(2\)</span> 阶时,这一轮可选择跳 <span class="arithmatex">\(1\)</span> 阶或跳 <span class="arithmatex">\(2\)</span></li>
</ul>
<p>在该定义下,<span class="arithmatex">\(dp[i, j]\)</span> 表示状态 <span class="arithmatex">\([i, j]\)</span> 对应的方案数。在该定义下的状态转移方程为:</p>
<div class="arithmatex">\[

View File

@@ -3522,10 +3522,10 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
<h3 id="_1">方法一:暴力搜索<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
<p>从状态 <span class="arithmatex">\([i, j]\)</span> 开始搜索,不断分解为更小的状态 <span class="arithmatex">\([i-1, j]\)</span><span class="arithmatex">\([i, j-1]\)</span> ,包括以下递归要素:</p>
<ul>
<li><strong>递归参数</strong>:状态 <span class="arithmatex">\([i, j]\)</span> </li>
<li><strong>返回值</strong>:从 <span class="arithmatex">\([0, 0]\)</span><span class="arithmatex">\([i, j]\)</span> 的最小路径和 <span class="arithmatex">\(dp[i, j]\)</span> </li>
<li><strong>终止条件</strong>:当 <span class="arithmatex">\(i = 0\)</span><span class="arithmatex">\(j = 0\)</span> 时,返回代价 <span class="arithmatex">\(grid[0, 0]\)</span> </li>
<li><strong>剪枝</strong>:当 <span class="arithmatex">\(i &lt; 0\)</span> 时或 <span class="arithmatex">\(j &lt; 0\)</span> 时索引越界,此时返回代价 <span class="arithmatex">\(+\infty\)</span> ,代表不可行</li>
<li><strong>递归参数</strong>:状态 <span class="arithmatex">\([i, j]\)</span> </li>
<li><strong>返回值</strong>:从 <span class="arithmatex">\([0, 0]\)</span><span class="arithmatex">\([i, j]\)</span> 的最小路径和 <span class="arithmatex">\(dp[i, j]\)</span> </li>
<li><strong>终止条件</strong>:当 <span class="arithmatex">\(i = 0\)</span><span class="arithmatex">\(j = 0\)</span> 时,返回代价 <span class="arithmatex">\(grid[0, 0]\)</span> </li>
<li><strong>剪枝</strong>:当 <span class="arithmatex">\(i &lt; 0\)</span> 时或 <span class="arithmatex">\(j &lt; 0\)</span> 时索引越界,此时返回代价 <span class="arithmatex">\(+\infty\)</span> ,代表不可行</li>
</ul>
<div class="tabbed-set tabbed-alternate" data-tabs="1:11"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JavaScript</label><label for="__tabbed_1_6">TypeScript</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label><label for="__tabbed_1_11">Dart</label></div>
<div class="tabbed-content">

View File

@@ -3402,8 +3402,8 @@
<p>每一轮的决策是对字符串 <span class="arithmatex">\(s\)</span> 进行一次编辑操作。</p>
<p>我们希望在编辑操作的过程中,问题的规模逐渐缩小,这样才能构建子问题。设字符串 <span class="arithmatex">\(s\)</span><span class="arithmatex">\(t\)</span> 的长度分别为 <span class="arithmatex">\(n\)</span><span class="arithmatex">\(m\)</span> ,我们先考虑两字符串尾部的字符 <span class="arithmatex">\(s[n-1]\)</span><span class="arithmatex">\(t[m-1]\)</span> </p>
<ul>
<li><span class="arithmatex">\(s[n-1]\)</span><span class="arithmatex">\(t[m-1]\)</span> 相同,我们可以跳过它们,直接考虑 <span class="arithmatex">\(s[n-2]\)</span><span class="arithmatex">\(t[m-2]\)</span> ;</li>
<li><span class="arithmatex">\(s[n-1]\)</span><span class="arithmatex">\(t[m-1]\)</span> 不同,我们需要对 <span class="arithmatex">\(s\)</span> 进行一次编辑(插入、删除、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题</li>
<li><span class="arithmatex">\(s[n-1]\)</span><span class="arithmatex">\(t[m-1]\)</span> 相同,我们可以跳过它们,直接考虑 <span class="arithmatex">\(s[n-2]\)</span><span class="arithmatex">\(t[m-2]\)</span> </li>
<li><span class="arithmatex">\(s[n-1]\)</span><span class="arithmatex">\(t[m-1]\)</span> 不同,我们需要对 <span class="arithmatex">\(s\)</span> 进行一次编辑(插入、删除、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题</li>
</ul>
<p>也就是说,我们在字符串 <span class="arithmatex">\(s\)</span> 中进行的每一轮决策(编辑操作),都会使得 <span class="arithmatex">\(s\)</span><span class="arithmatex">\(t\)</span> 中剩余的待匹配字符发生变化。因此,状态为当前在 <span class="arithmatex">\(s\)</span> , <span class="arithmatex">\(t\)</span> 中考虑的第 <span class="arithmatex">\(i\)</span> , <span class="arithmatex">\(j\)</span> 个字符,记为 <span class="arithmatex">\([i, j]\)</span></p>
<p>状态 <span class="arithmatex">\([i, j]\)</span> 对应的子问题:<strong><span class="arithmatex">\(s\)</span> 的前 <span class="arithmatex">\(i\)</span> 个字符更改为 <span class="arithmatex">\(t\)</span> 的前 <span class="arithmatex">\(j\)</span> 个字符所需的最少编辑步数</strong></p>
@@ -3411,9 +3411,9 @@
<p><strong>第二步:找出最优子结构,进而推导出状态转移方程</strong></p>
<p>考虑子问题 <span class="arithmatex">\(dp[i, j]\)</span> ,其对应的两个字符串的尾部字符为 <span class="arithmatex">\(s[i-1]\)</span><span class="arithmatex">\(t[j-1]\)</span> ,可根据不同编辑操作分为三种情况:</p>
<ol>
<li><span class="arithmatex">\(s[i-1]\)</span> 之后添加 <span class="arithmatex">\(t[j-1]\)</span> ,则剩余子问题 <span class="arithmatex">\(dp[i, j-1]\)</span> </li>
<li>删除 <span class="arithmatex">\(s[i-1]\)</span> ,则剩余子问题 <span class="arithmatex">\(dp[i-1, j]\)</span> </li>
<li><span class="arithmatex">\(s[i-1]\)</span> 替换为 <span class="arithmatex">\(t[j-1]\)</span> ,则剩余子问题 <span class="arithmatex">\(dp[i-1, j-1]\)</span> </li>
<li><span class="arithmatex">\(s[i-1]\)</span> 之后添加 <span class="arithmatex">\(t[j-1]\)</span> ,则剩余子问题 <span class="arithmatex">\(dp[i, j-1]\)</span> </li>
<li>删除 <span class="arithmatex">\(s[i-1]\)</span> ,则剩余子问题 <span class="arithmatex">\(dp[i-1, j]\)</span> </li>
<li><span class="arithmatex">\(s[i-1]\)</span> 替换为 <span class="arithmatex">\(t[j-1]\)</span> ,则剩余子问题 <span class="arithmatex">\(dp[i-1, j-1]\)</span> </li>
</ol>
<p><img alt="编辑距离的状态转移" src="../edit_distance_problem.assets/edit_distance_state_transfer.png" /></p>
<p align="center"> Fig. 编辑距离的状态转移 </p>

View File

@@ -3825,8 +3825,8 @@ dp[i] = dp[i-1] + dp[i-2]
<h2 id="1412">14.1.2. &nbsp; 方法二:记忆化搜索<a class="headerlink" href="#1412" title="Permanent link">&para;</a></h2>
<p>为了提升算法效率,<strong>我们希望所有的重叠子问题都只被计算一次</strong>。为此,我们声明一个数组 <code>mem</code> 来记录每个子问题的解,并在搜索过程中这样做:</p>
<ol>
<li>当首次计算 <span class="arithmatex">\(dp[i]\)</span> 时,我们将其记录至 <code>mem[i]</code> ,以便之后使用</li>
<li>当再次需要计算 <span class="arithmatex">\(dp[i]\)</span> 时,我们便可直接从 <code>mem[i]</code> 中获取结果,从而将重叠子问题剪枝</li>
<li>当首次计算 <span class="arithmatex">\(dp[i]\)</span> 时,我们将其记录至 <code>mem[i]</code> ,以便之后使用</li>
<li>当再次需要计算 <span class="arithmatex">\(dp[i]\)</span> 时,我们便可直接从 <code>mem[i]</code> 中获取结果,从而将重叠子问题剪枝</li>
</ol>
<div class="tabbed-set tabbed-alternate" data-tabs="3:11"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Java</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Python</label><label for="__tabbed_3_4">Go</label><label for="__tabbed_3_5">JavaScript</label><label for="__tabbed_3_6">TypeScript</label><label for="__tabbed_3_7">C</label><label for="__tabbed_3_8">C#</label><label for="__tabbed_3_9">Swift</label><label for="__tabbed_3_10">Zig</label><label for="__tabbed_3_11">Dart</label></div>
<div class="tabbed-content">
@@ -4191,9 +4191,9 @@ dp[i] = dp[i-1] + dp[i-2]
<p>与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的某个特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如,爬楼梯问题的状态定义为当前所在楼梯阶数 <span class="arithmatex">\(i\)</span></p>
<p>总结以上,动态规划的常用术语包括:</p>
<ul>
<li>将数组 <code>dp</code> 称为「<span class="arithmatex">\(dp\)</span> 表」,<span class="arithmatex">\(dp[i]\)</span> 表示状态 <span class="arithmatex">\(i\)</span> 对应子问题的解</li>
<li>将最小子问题对应的状态(即第 <span class="arithmatex">\(1\)</span> , <span class="arithmatex">\(2\)</span> 阶楼梯)称为「初始状态」</li>
<li>将递推公式 <span class="arithmatex">\(dp[i] = dp[i-1] + dp[i-2]\)</span> 称为「状态转移方程」</li>
<li>将数组 <code>dp</code> 称为「<span class="arithmatex">\(dp\)</span> 表」,<span class="arithmatex">\(dp[i]\)</span> 表示状态 <span class="arithmatex">\(i\)</span> 对应子问题的解</li>
<li>将最小子问题对应的状态(即第 <span class="arithmatex">\(1\)</span> , <span class="arithmatex">\(2\)</span> 阶楼梯)称为「初始状态」</li>
<li>将递推公式 <span class="arithmatex">\(dp[i] = dp[i-1] + dp[i-2]\)</span> 称为「状态转移方程」</li>
</ul>
<p><img alt="爬楼梯的动态规划过程" src="../intro_to_dynamic_programming.assets/climbing_stairs_dp.png" /></p>
<p align="center"> Fig. 爬楼梯的动态规划过程 </p>

View File

@@ -3429,8 +3429,8 @@
<p><strong>第二步:找出最优子结构,进而推导出状态转移方程</strong></p>
<p>当我们做出物品 <span class="arithmatex">\(i\)</span> 的决策后,剩余的是前 <span class="arithmatex">\(i-1\)</span> 个物品的决策。因此,状态转移分为两种情况:</p>
<ul>
<li><strong>不放入物品 <span class="arithmatex">\(i\)</span></strong> :背包容量不变,状态转移至 <span class="arithmatex">\([i-1, c]\)</span> </li>
<li><strong>放入物品 <span class="arithmatex">\(i\)</span></strong> :背包容量减小 <span class="arithmatex">\(wgt[i-1]\)</span> ,价值增加 <span class="arithmatex">\(val[i-1]\)</span> ,状态转移至 <span class="arithmatex">\([i-1, c-wgt[i-1]]\)</span> </li>
<li><strong>不放入物品 <span class="arithmatex">\(i\)</span></strong> :背包容量不变,状态转移至 <span class="arithmatex">\([i-1, c]\)</span> </li>
<li><strong>放入物品 <span class="arithmatex">\(i\)</span></strong> :背包容量减小 <span class="arithmatex">\(wgt[i-1]\)</span> ,价值增加 <span class="arithmatex">\(val[i-1]\)</span> ,状态转移至 <span class="arithmatex">\([i-1, c-wgt[i-1]]\)</span> </li>
</ul>
<p>上述的状态转移向我们揭示了本题的最优子结构:<strong>最大价值 <span class="arithmatex">\(dp[i, c]\)</span> 等于不放入物品 <span class="arithmatex">\(i\)</span> 和放入物品 <span class="arithmatex">\(i\)</span> 两种方案中的价值更大的那一个</strong>。由此可推出状态转移方程:</p>
<div class="arithmatex">\[
@@ -3444,10 +3444,10 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
<h3 id="_1">方法一:暴力搜索<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
<p>搜索代码包含以下要素:</p>
<ul>
<li><strong>递归参数</strong>:状态 <span class="arithmatex">\([i, c]\)</span> </li>
<li><strong>返回值</strong>:子问题的解 <span class="arithmatex">\(dp[i, c]\)</span> </li>
<li><strong>终止条件</strong>:当物品编号越界 <span class="arithmatex">\(i = 0\)</span> 或背包剩余容量为 <span class="arithmatex">\(0\)</span> 时,终止递归并返回价值 <span class="arithmatex">\(0\)</span> </li>
<li><strong>剪枝</strong>:若当前物品重量超出背包剩余容量,则只能不放入背包</li>
<li><strong>递归参数</strong>:状态 <span class="arithmatex">\([i, c]\)</span> </li>
<li><strong>返回值</strong>:子问题的解 <span class="arithmatex">\(dp[i, c]\)</span> </li>
<li><strong>终止条件</strong>:当物品编号越界 <span class="arithmatex">\(i = 0\)</span> 或背包剩余容量为 <span class="arithmatex">\(0\)</span> 时,终止递归并返回价值 <span class="arithmatex">\(0\)</span> </li>
<li><strong>剪枝</strong>:若当前物品重量超出背包剩余容量,则只能不放入背包</li>
</ul>
<div class="tabbed-set tabbed-alternate" data-tabs="1:11"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JavaScript</label><label for="__tabbed_1_6">TypeScript</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label><label for="__tabbed_1_11">Dart</label></div>
<div class="tabbed-content">

View File

@@ -3527,13 +3527,13 @@
<p>完全背包和 0-1 背包问题非常相似,<strong>区别仅在于不限制物品的选择次数</strong></p>
<ul>
<li>在 0-1 背包中,每个物品只有一个,因此将物品 <span class="arithmatex">\(i\)</span> 放入背包后,只能从前 <span class="arithmatex">\(i-1\)</span> 个物品中选择</li>
<li>在完全背包中,每个物品有无数个,因此将物品 <span class="arithmatex">\(i\)</span> 放入背包后,<strong>仍可以从前 <span class="arithmatex">\(i\)</span> 个物品中选择</strong></li>
<li>在 0-1 背包中,每个物品只有一个,因此将物品 <span class="arithmatex">\(i\)</span> 放入背包后,只能从前 <span class="arithmatex">\(i-1\)</span> 个物品中选择</li>
<li>在完全背包中,每个物品有无数个,因此将物品 <span class="arithmatex">\(i\)</span> 放入背包后,<strong>仍可以从前 <span class="arithmatex">\(i\)</span> 个物品中选择</strong></li>
</ul>
<p>这就导致了状态转移的变化,对于状态 <span class="arithmatex">\([i, c]\)</span> 有:</p>
<ul>
<li><strong>不放入物品 <span class="arithmatex">\(i\)</span></strong> :与 0-1 背包相同,转移至 <span class="arithmatex">\([i-1, c]\)</span> </li>
<li><strong>放入物品 <span class="arithmatex">\(i\)</span></strong> :与 0-1 背包不同,转移至 <span class="arithmatex">\([i, c-wgt[i-1]]\)</span> </li>
<li><strong>不放入物品 <span class="arithmatex">\(i\)</span></strong> :与 0-1 背包相同,转移至 <span class="arithmatex">\([i-1, c]\)</span> </li>
<li><strong>放入物品 <span class="arithmatex">\(i\)</span></strong> :与 0-1 背包不同,转移至 <span class="arithmatex">\([i, c-wgt[i-1]]\)</span> </li>
</ul>
<p>从而状态转移方程变为:</p>
<div class="arithmatex">\[
@@ -3922,9 +3922,9 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
<p><strong>零钱兑换可以看作是完全背包的一种特殊情况</strong>,两者具有以下联系与不同点:</p>
<ul>
<li>两道题可以相互转换,“物品”对应于“硬币”、“物品重量”对应于“硬币面值”、“背包容量”对应于“目标金额”</li>
<li>优化目标相反,背包问题是要最大化物品价值,零钱兑换问题是要最小化硬币数量</li>
<li>背包问题是求“不超过”背包容量下的解,零钱兑换是求“恰好”凑到目标金额的解</li>
<li>两道题可以相互转换,“物品”对应于“硬币”、“物品重量”对应于“硬币面值”、“背包容量”对应于“目标金额”</li>
<li>优化目标相反,背包问题是要最大化物品价值,零钱兑换问题是要最小化硬币数量</li>
<li>背包问题是求“不超过”背包容量下的解,零钱兑换是求“恰好”凑到目标金额的解</li>
</ul>
<p><strong>第一步:思考每轮的决策,定义状态,从而得到 <span class="arithmatex">\(dp\)</span></strong></p>
<p>状态 <span class="arithmatex">\([i, a]\)</span> 对应的子问题为:<strong><span class="arithmatex">\(i\)</span> 个硬币能够凑出金额 <span class="arithmatex">\(a\)</span> 的最少硬币个数</strong>,记为 <span class="arithmatex">\(dp[i, a]\)</span></p>
@@ -3932,8 +3932,8 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
<p><strong>第二步:找出最优子结构,进而推导出状态转移方程</strong></p>
<p>与完全背包的状态转移方程基本相同,不同点在于:</p>
<ul>
<li>本题要求最小值,因此需将运算符 <span class="arithmatex">\(\max()\)</span> 更改为 <span class="arithmatex">\(\min()\)</span> </li>
<li>优化主体是硬币数量而非商品价值,因此在选中硬币时执行 <span class="arithmatex">\(+1\)</span> 即可</li>
<li>本题要求最小值,因此需将运算符 <span class="arithmatex">\(\max()\)</span> 更改为 <span class="arithmatex">\(\min()\)</span> </li>
<li>优化主体是硬币数量而非商品价值,因此在选中硬币时执行 <span class="arithmatex">\(+1\)</span> 即可</li>
</ul>
<div class="arithmatex">\[
dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)