This commit is contained in:
krahets
2023-08-20 13:37:20 +08:00
parent 88e0b11361
commit 96fded547b
35 changed files with 777 additions and 716 deletions

View File

@@ -2906,15 +2906,22 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_1" class="md-nav__link">
代码实现
<a href="#1" class="md-nav__link">
1. &nbsp; 动态规划思路
</a>
</li>
<li class="md-nav__item">
<a href="#_2" class="md-nav__link">
状态压缩
<a href="#2" class="md-nav__link">
2. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#3" class="md-nav__link">
3. &nbsp; 状态压缩
</a>
</li>
@@ -2933,15 +2940,22 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_3" class="md-nav__link">
代码实现
<a href="#1_1" class="md-nav__link">
1. &nbsp; 动态规划思路
</a>
</li>
<li class="md-nav__item">
<a href="#_4" class="md-nav__link">
状态压缩
<a href="#2_1" class="md-nav__link">
2. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#3_1" class="md-nav__link">
3. &nbsp; 状态压缩
</a>
</li>
@@ -2960,15 +2974,22 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_5" class="md-nav__link">
代码实现
<a href="#1_2" class="md-nav__link">
1. &nbsp; 动态规划思路
</a>
</li>
<li class="md-nav__item">
<a href="#_6" class="md-nav__link">
状态压缩
<a href="#2_2" class="md-nav__link">
2. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#3_2" class="md-nav__link">
3. &nbsp; 状态压缩
</a>
</li>
@@ -3458,15 +3479,22 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_1" class="md-nav__link">
代码实现
<a href="#1" class="md-nav__link">
1. &nbsp; 动态规划思路
</a>
</li>
<li class="md-nav__item">
<a href="#_2" class="md-nav__link">
状态压缩
<a href="#2" class="md-nav__link">
2. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#3" class="md-nav__link">
3. &nbsp; 状态压缩
</a>
</li>
@@ -3485,15 +3513,22 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_3" class="md-nav__link">
代码实现
<a href="#1_1" class="md-nav__link">
1. &nbsp; 动态规划思路
</a>
</li>
<li class="md-nav__item">
<a href="#_4" class="md-nav__link">
状态压缩
<a href="#2_1" class="md-nav__link">
2. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#3_1" class="md-nav__link">
3. &nbsp; 状态压缩
</a>
</li>
@@ -3512,15 +3547,22 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_5" class="md-nav__link">
代码实现
<a href="#1_2" class="md-nav__link">
1. &nbsp; 动态规划思路
</a>
</li>
<li class="md-nav__item">
<a href="#_6" class="md-nav__link">
状态压缩
<a href="#2_2" class="md-nav__link">
2. &nbsp; 代码实现
</a>
</li>
<li class="md-nav__item">
<a href="#3_2" class="md-nav__link">
3. &nbsp; 状态压缩
</a>
</li>
@@ -3563,6 +3605,7 @@
<p><img alt="完全背包问题的示例数据" src="../unbounded_knapsack_problem.assets/unbounded_knapsack_example.png" /></p>
<p align="center"> 图:完全背包问题的示例数据 </p>
<h3 id="1">1. &nbsp; 动态规划思路<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<p>完全背包和 0-1 背包问题非常相似,<strong>区别仅在于不限制物品的选择次数</strong></p>
<ul>
<li>在 0-1 背包中,每个物品只有一个,因此将物品 <span class="arithmatex">\(i\)</span> 放入背包后,只能从前 <span class="arithmatex">\(i-1\)</span> 个物品中选择。</li>
@@ -3577,7 +3620,7 @@
<div class="arithmatex">\[
dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
\]</div>
<h3 id="_1">代码实现<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
<h3 id="2">2. &nbsp; 代码实现<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<p>对比两道题目的代码,状态转移中有一处从 <span class="arithmatex">\(i-1\)</span> 变为 <span class="arithmatex">\(i\)</span> ,其余完全一致。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:12"><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" /><input id="__tabbed_1_12" 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">JS</label><label for="__tabbed_1_6">TS</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><label for="__tabbed_1_12">Rust</label></div>
<div class="tabbed-content">
@@ -3792,7 +3835,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
</div>
</div>
</div>
<h3 id="_2">状态压缩<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<h3 id="3">3. &nbsp; 状态压缩<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>由于当前状态是从左边和上边的状态转移而来,<strong>因此状态压缩后应该对 <span class="arithmatex">\(dp\)</span> 表中的每一行采取正序遍历</strong></p>
<p>这个遍历顺序与 0-1 背包正好相反。请通过以下动画来理解两者的区别。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:6"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">&lt;1&gt;</label><label for="__tabbed_2_2">&lt;2&gt;</label><label for="__tabbed_2_3">&lt;3&gt;</label><label for="__tabbed_2_4">&lt;4&gt;</label><label for="__tabbed_2_5">&lt;5&gt;</label><label for="__tabbed_2_6">&lt;6&gt;</label></div>
@@ -4040,6 +4083,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
<p><img alt="零钱兑换问题的示例数据" src="../unbounded_knapsack_problem.assets/coin_change_example.png" /></p>
<p align="center"> 图:零钱兑换问题的示例数据 </p>
<h3 id="1_1">1. &nbsp; 动态规划思路<a class="headerlink" href="#1_1" title="Permanent link">&para;</a></h3>
<p><strong>零钱兑换可以看作是完全背包的一种特殊情况</strong>,两者具有以下联系与不同点:</p>
<ul>
<li>两道题可以相互转换,“物品”对应于“硬币”、“物品重量”对应于“硬币面值”、“背包容量”对应于“目标金额”。</li>
@@ -4061,7 +4105,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
<p><strong>第三步:确定边界条件和状态转移顺序</strong></p>
<p>当目标金额为 <span class="arithmatex">\(0\)</span> 时,凑出它的最少硬币个数为 <span class="arithmatex">\(0\)</span> ,即首列所有 <span class="arithmatex">\(dp[i, 0]\)</span> 都等于 <span class="arithmatex">\(0\)</span></p>
<p>当无硬币时,<strong>无法凑出任意 <span class="arithmatex">\(&gt; 0\)</span> 的目标金额</strong>,即是无效解。为使状态转移方程中的 <span class="arithmatex">\(\min()\)</span> 函数能够识别并过滤无效解,我们考虑使用 <span class="arithmatex">\(+ \infty\)</span> 来表示它们,即令首行所有 <span class="arithmatex">\(dp[0, a]\)</span> 都等于 <span class="arithmatex">\(+ \infty\)</span></p>
<h3 id="_3">代码实现<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
<h3 id="2_1">2. &nbsp; 代码实现<a class="headerlink" href="#2_1" title="Permanent link">&para;</a></h3>
<p>大多数编程语言并未提供 <span class="arithmatex">\(+ \infty\)</span> 变量,只能使用整型 <code>int</code> 的最大值来代替。而这又会导致大数越界:状态转移方程中的 <span class="arithmatex">\(+ 1\)</span> 操作可能发生溢出。</p>
<p>为此,我们采用数字 <span class="arithmatex">\(amt + 1\)</span> 来表示无效解,因为凑出 <span class="arithmatex">\(amt\)</span> 的硬币个数最多为 <span class="arithmatex">\(amt\)</span> 个。</p>
<p>最后返回前,判断 <span class="arithmatex">\(dp[n, amt]\)</span> 是否等于 <span class="arithmatex">\(amt + 1\)</span> ,若是则返回 <span class="arithmatex">\(-1\)</span> ,代表无法凑出目标金额。</p>
@@ -4381,7 +4425,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
</div>
<p align="center"> 图:零钱兑换问题的动态规划过程 </p>
<h3 id="_4">状态压缩<a class="headerlink" href="#_4" title="Permanent link">&para;</a></h3>
<h3 id="3_1">3. &nbsp; 状态压缩<a class="headerlink" href="#3_1" title="Permanent link">&para;</a></h3>
<p>零钱兑换的状态压缩的处理方式和完全背包一致。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="6:12"><input checked="checked" id="__tabbed_6_1" name="__tabbed_6" type="radio" /><input id="__tabbed_6_2" name="__tabbed_6" type="radio" /><input id="__tabbed_6_3" name="__tabbed_6" type="radio" /><input id="__tabbed_6_4" name="__tabbed_6" type="radio" /><input id="__tabbed_6_5" name="__tabbed_6" type="radio" /><input id="__tabbed_6_6" name="__tabbed_6" type="radio" /><input id="__tabbed_6_7" name="__tabbed_6" type="radio" /><input id="__tabbed_6_8" name="__tabbed_6" type="radio" /><input id="__tabbed_6_9" name="__tabbed_6" type="radio" /><input id="__tabbed_6_10" name="__tabbed_6" type="radio" /><input id="__tabbed_6_11" name="__tabbed_6" type="radio" /><input id="__tabbed_6_12" name="__tabbed_6" type="radio" /><div class="tabbed-labels"><label for="__tabbed_6_1">Java</label><label for="__tabbed_6_2">C++</label><label for="__tabbed_6_3">Python</label><label for="__tabbed_6_4">Go</label><label for="__tabbed_6_5">JS</label><label for="__tabbed_6_6">TS</label><label for="__tabbed_6_7">C</label><label for="__tabbed_6_8">C#</label><label for="__tabbed_6_9">Swift</label><label for="__tabbed_6_10">Zig</label><label for="__tabbed_6_11">Dart</label><label for="__tabbed_6_12">Rust</label></div>
<div class="tabbed-content">
@@ -4634,13 +4678,14 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
<p><img alt="零钱兑换问题 II 的示例数据" src="../unbounded_knapsack_problem.assets/coin_change_ii_example.png" /></p>
<p align="center"> 图:零钱兑换问题 II 的示例数据 </p>
<h3 id="1_2">1. &nbsp; 动态规划思路<a class="headerlink" href="#1_2" title="Permanent link">&para;</a></h3>
<p>相比于上一题,本题目标是组合数量,因此子问题变为:<strong><span class="arithmatex">\(i\)</span> 种硬币能够凑出金额 <span class="arithmatex">\(a\)</span> 的组合数量</strong>。而 <span class="arithmatex">\(dp\)</span> 表仍然是尺寸为 <span class="arithmatex">\((n+1) \times (amt + 1)\)</span> 的二维矩阵。</p>
<p>当前状态的组合数量等于不选当前硬币与选当前硬币这两种决策的组合数量之和。状态转移方程为:</p>
<div class="arithmatex">\[
dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]]
\]</div>
<p>当目标金额为 <span class="arithmatex">\(0\)</span> 时,无需选择任何硬币即可凑出目标金额,因此应将首列所有 <span class="arithmatex">\(dp[i, 0]\)</span> 都初始化为 <span class="arithmatex">\(1\)</span> 。当无硬币时,无法凑出任何 <span class="arithmatex">\(&gt;0\)</span> 的目标金额,因此首行所有 <span class="arithmatex">\(dp[0, a]\)</span> 都等于 <span class="arithmatex">\(0\)</span></p>
<h3 id="_5">代码实现<a class="headerlink" href="#_5" title="Permanent link">&para;</a></h3>
<h3 id="2_2">2. &nbsp; 代码实现<a class="headerlink" href="#2_2" title="Permanent link">&para;</a></h3>
<div class="tabbed-set tabbed-alternate" data-tabs="7:12"><input checked="checked" id="__tabbed_7_1" name="__tabbed_7" type="radio" /><input id="__tabbed_7_2" name="__tabbed_7" type="radio" /><input id="__tabbed_7_3" name="__tabbed_7" type="radio" /><input id="__tabbed_7_4" name="__tabbed_7" type="radio" /><input id="__tabbed_7_5" name="__tabbed_7" type="radio" /><input id="__tabbed_7_6" name="__tabbed_7" type="radio" /><input id="__tabbed_7_7" name="__tabbed_7" type="radio" /><input id="__tabbed_7_8" name="__tabbed_7" type="radio" /><input id="__tabbed_7_9" name="__tabbed_7" type="radio" /><input id="__tabbed_7_10" name="__tabbed_7" type="radio" /><input id="__tabbed_7_11" name="__tabbed_7" type="radio" /><input id="__tabbed_7_12" name="__tabbed_7" type="radio" /><div class="tabbed-labels"><label for="__tabbed_7_1">Java</label><label for="__tabbed_7_2">C++</label><label for="__tabbed_7_3">Python</label><label for="__tabbed_7_4">Go</label><label for="__tabbed_7_5">JS</label><label for="__tabbed_7_6">TS</label><label for="__tabbed_7_7">C</label><label for="__tabbed_7_8">C#</label><label for="__tabbed_7_9">Swift</label><label for="__tabbed_7_10">Zig</label><label for="__tabbed_7_11">Dart</label><label for="__tabbed_7_12">Rust</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4889,7 +4934,7 @@ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]]
</div>
</div>
</div>
<h3 id="_6">状态压缩<a class="headerlink" href="#_6" title="Permanent link">&para;</a></h3>
<h3 id="3_2">3. &nbsp; 状态压缩<a class="headerlink" href="#3_2" title="Permanent link">&para;</a></h3>
<p>状态压缩处理方式相同,删除硬币维度即可。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="8:12"><input checked="checked" id="__tabbed_8_1" name="__tabbed_8" type="radio" /><input id="__tabbed_8_2" name="__tabbed_8" type="radio" /><input id="__tabbed_8_3" name="__tabbed_8" type="radio" /><input id="__tabbed_8_4" name="__tabbed_8" type="radio" /><input id="__tabbed_8_5" name="__tabbed_8" type="radio" /><input id="__tabbed_8_6" name="__tabbed_8" type="radio" /><input id="__tabbed_8_7" name="__tabbed_8" type="radio" /><input id="__tabbed_8_8" name="__tabbed_8" type="radio" /><input id="__tabbed_8_9" name="__tabbed_8" type="radio" /><input id="__tabbed_8_10" name="__tabbed_8" type="radio" /><input id="__tabbed_8_11" name="__tabbed_8" type="radio" /><input id="__tabbed_8_12" name="__tabbed_8" type="radio" /><div class="tabbed-labels"><label for="__tabbed_8_1">Java</label><label for="__tabbed_8_2">C++</label><label for="__tabbed_8_3">Python</label><label for="__tabbed_8_4">Go</label><label for="__tabbed_8_5">JS</label><label for="__tabbed_8_6">TS</label><label for="__tabbed_8_7">C</label><label for="__tabbed_8_8">C#</label><label for="__tabbed_8_9">Swift</label><label for="__tabbed_8_10">Zig</label><label for="__tabbed_8_11">Dart</label><label for="__tabbed_8_12">Rust</label></div>
<div class="tabbed-content">