This commit is contained in:
krahets
2023-07-21 21:53:15 +08:00
parent c64dcd39e7
commit 872edb67c1
109 changed files with 11092 additions and 111 deletions

View File

@ -2812,30 +2812,43 @@
<li class="md-nav__item">
<a href="#1432" class="md-nav__link">
14.3.2. &nbsp; 问题求解
14.3.2. &nbsp; 问题求解步骤
</a>
<nav class="md-nav" aria-label="14.3.2.   问题求解步骤">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_1" class="md-nav__link">
方法一:暴力搜索
</a>
</li>
<li class="md-nav__item">
<a href="#1433" class="md-nav__link">
14.3.3. &nbsp; 方法一:暴力搜索
<li class="md-nav__item">
<a href="#_2" class="md-nav__link">
方法二:记忆化搜索
</a>
</li>
<li class="md-nav__item">
<a href="#1434" class="md-nav__link">
14.3.4. &nbsp; 方法二:记忆化搜索
<li class="md-nav__item">
<a href="#_3" class="md-nav__link">
方法三:动态规划
</a>
</li>
<li class="md-nav__item">
<a href="#1435" class="md-nav__link">
14.3.5. &nbsp; 方法三:动态规划
<li class="md-nav__item">
<a href="#_4" class="md-nav__link">
状态压缩
</a>
</li>
</ul>
</nav>
</li>
</ul>
@ -2981,6 +2994,8 @@
@ -3116,6 +3131,34 @@
<li class="md-nav__item">
<a href="../../chapter_greedy/max_product_cutting_problem/" class="md-nav__link">
<span class="md-ellipsis">
15.4. &nbsp; 最大切分乘积问题
</span>
<span class="md-status md-status--new" title="最近添加">
</span>
</a>
</li>
</ul>
</nav>
@ -3314,30 +3357,43 @@
<li class="md-nav__item">
<a href="#1432" class="md-nav__link">
14.3.2. &nbsp; 问题求解
14.3.2. &nbsp; 问题求解步骤
</a>
<nav class="md-nav" aria-label="14.3.2.   问题求解步骤">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_1" class="md-nav__link">
方法一:暴力搜索
</a>
</li>
<li class="md-nav__item">
<a href="#1433" class="md-nav__link">
14.3.3. &nbsp; 方法一:暴力搜索
<li class="md-nav__item">
<a href="#_2" class="md-nav__link">
方法二:记忆化搜索
</a>
</li>
<li class="md-nav__item">
<a href="#1434" class="md-nav__link">
14.3.4. &nbsp; 方法二:记忆化搜索
<li class="md-nav__item">
<a href="#_3" class="md-nav__link">
方法三:动态规划
</a>
</li>
<li class="md-nav__item">
<a href="#1435" class="md-nav__link">
14.3.5. &nbsp; 方法三:动态规划
<li class="md-nav__item">
<a href="#_4" class="md-nav__link">
状态压缩
</a>
</li>
</ul>
</nav>
</li>
</ul>
@ -3384,7 +3440,7 @@
<li>问题描述中有明显的排列组合的特征,需要返回具体的多个方案。</li>
</ul>
<p>如果一个问题满足决策树模型,并具有较为明显的“加分项“,我们就可以假设它是一个动态规划问题,并尝试求解它。</p>
<h2 id="1432">14.3.2. &nbsp; 问题求解<a class="headerlink" href="#1432" title="Permanent link">&para;</a></h2>
<h2 id="1432">14.3.2. &nbsp; 问题求解步骤<a class="headerlink" href="#1432" title="Permanent link">&para;</a></h2>
<p>动态规划的解题流程可能会因问题的性质和难度而有所不同,但通常遵循以下步骤:描述决策,定义状态,建立 <span class="arithmatex">\(dp\)</span> 表,推导状态转移方程,确定边界条件等。</p>
<p>为了更形象地展示解题步骤,我们使用一个经典问题「最小路径和」来举例。</p>
<div class="admonition question">
@ -3432,7 +3488,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
<p>边界条件即初始状态,在搜索中用于剪枝,在动态规划中用于初始化 <span class="arithmatex">\(dp\)</span> 表。状态转移顺序的核心是要保证在计算当前问题时,所有它依赖的更小子问题都已经被正确地计算出来。</p>
</div>
<p>接下来,我们就可以实现动态规划代码了。然而,由于子问题分解是一种从顶至底的思想,因此按照“暴力搜索 <span class="arithmatex">\(\rightarrow\)</span> 记忆化搜索 <span class="arithmatex">\(\rightarrow\)</span> 动态规划”的顺序实现更加符合思维习惯。</p>
<h2 id="1433">14.3.3. &nbsp; 方法一:暴力搜索<a class="headerlink" href="#1433" title="Permanent link">&para;</a></h2>
<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> <strong>返回值</strong>:从 <span class="arithmatex">\([0, 0]\)</span><span class="arithmatex">\([i, j]\)</span> 的最小路径和 <span class="arithmatex">\(dp[i, j]\)</span> </li>
@ -3580,7 +3636,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
<p align="center"> Fig. 暴力搜索递归树 </p>
<p>每个状态都有向下和向右两种选择,从左上角走到右下角总共需要 <span class="arithmatex">\(m + n - 2\)</span> 步,所以最差时间复杂度为 <span class="arithmatex">\(O(2^{m + n})\)</span> 。请注意,这种计算方式未考虑临近网格边界的情况,当到达网络边界时只剩下一种选择。因此实际的路径数量会少一些。</p>
<h2 id="1434">14.3.4. &nbsp; 方法二:记忆化搜索<a class="headerlink" href="#1434" title="Permanent link">&para;</a></h2>
<h3 id="_2">方法二:记忆化搜索<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<p>为了避免重复计算重叠子问题,我们引入一个和网格 <code>grid</code> 相同尺寸的记忆列表 <code>mem</code> ,用于记录各个子问题的解,提升搜索效率。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:11"><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" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Java</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Python</label><label for="__tabbed_2_4">Go</label><label for="__tabbed_2_5">JavaScript</label><label for="__tabbed_2_6">TypeScript</label><label for="__tabbed_2_7">C</label><label for="__tabbed_2_8">C#</label><label for="__tabbed_2_9">Swift</label><label for="__tabbed_2_10">Zig</label><label for="__tabbed_2_11">Dart</label></div>
<div class="tabbed-content">
@ -3753,7 +3809,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
<p><img alt="记忆化搜索递归树" src="../dp_solution_pipeline.assets/min_path_sum_dfs_mem.png" /></p>
<p align="center"> Fig. 记忆化搜索递归树 </p>
<h2 id="1435">14.3.5. &nbsp; 方法三:动态规划<a class="headerlink" href="#1435" title="Permanent link">&para;</a></h2>
<h3 id="_3">方法三:动态规划<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
<p>动态规划代码是从底至顶的,仅需循环即可实现。</p>
<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">
@ -3967,6 +4023,7 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
</div>
</div>
</div>
<h3 id="_4">状态压缩<a class="headerlink" href="#_4" title="Permanent link">&para;</a></h3>
<p>如果希望进一步节省空间使用,可以考虑进行状态压缩。每个格子只与左边和上边的格子有关,因此我们可以只用一个单行数组来实现 <span class="arithmatex">\(dp\)</span> 表。</p>
<p>由于数组 <code>dp</code> 只能表示一行的状态,因此我们无法提前初始化首列状态,而是在遍历每行中更新它。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="5:11"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><input id="__tabbed_5_11" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Java</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Python</label><label for="__tabbed_5_4">Go</label><label for="__tabbed_5_5">JavaScript</label><label for="__tabbed_5_6">TypeScript</label><label for="__tabbed_5_7">C</label><label for="__tabbed_5_8">C#</label><label for="__tabbed_5_9">Swift</label><label for="__tabbed_5_10">Zig</label><label for="__tabbed_5_11">Dart</label></div>