This commit is contained in:
krahets
2023-08-27 23:41:10 +08:00
parent 8c9cf3f087
commit 016f13d882
66 changed files with 262 additions and 270 deletions

View File

@@ -3483,16 +3483,16 @@
<h3 id="1">1. &nbsp; 动态规划思路<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<p><strong>第一步:思考每轮的决策,定义状态,从而得到 <span class="arithmatex">\(dp\)</span></strong></p>
<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>
<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>
</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">\(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>
<p>至此,得到一个尺寸为 <span class="arithmatex">\((i+1) \times (j+1)\)</span> 的二维 <span class="arithmatex">\(dp\)</span> 表。</p>
<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> ,可根据不同编辑操作分为图 14-29 所示的三种情况</p>
<p>考虑子问题 <span class="arithmatex">\(dp[i, j]\)</span> ,其对应的两个字符串的尾部字符为 <span class="arithmatex">\(s[i-1]\)</span><span class="arithmatex">\(t[j-1]\)</span> ,可根据不同编辑操作分为图 14-29 所示的三种情况</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>
@@ -3501,7 +3501,7 @@
<p><img alt="编辑距离的状态转移" src="../edit_distance_problem.assets/edit_distance_state_transfer.png" /></p>
<p align="center"> 图 14-29 &nbsp; 编辑距离的状态转移 </p>
<p>根据以上分析,可得最优子结构:<span class="arithmatex">\(dp[i, j]\)</span> 的最少编辑步数等于 <span class="arithmatex">\(dp[i, j-1]\)</span> , <span class="arithmatex">\(dp[i-1, j]\)</span> , <span class="arithmatex">\(dp[i-1, j-1]\)</span> 三者中的最少编辑步数,再加上本次的编辑步数 <span class="arithmatex">\(1\)</span> 。对应的状态转移方程为:</p>
<p>根据以上分析,可得最优子结构:<span class="arithmatex">\(dp[i, j]\)</span> 的最少编辑步数等于 <span class="arithmatex">\(dp[i, j-1]\)</span><span class="arithmatex">\(dp[i-1, j]\)</span><span class="arithmatex">\(dp[i-1, j-1]\)</span> 三者中的最少编辑步数,再加上本次的编辑步数 <span class="arithmatex">\(1\)</span> 。对应的状态转移方程为:</p>
<div class="arithmatex">\[
dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1
\]</div>
@@ -3834,7 +3834,7 @@ dp[i, j] = dp[i-1, j-1]
<p align="center"> 图 14-30 &nbsp; 编辑距离的动态规划过程 </p>
<h3 id="3">3. &nbsp; 空间优化<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>由于 <span class="arithmatex">\(dp[i,j]\)</span> 是由上方 <span class="arithmatex">\(dp[i-1, j]\)</span> 、左方 <span class="arithmatex">\(dp[i, j-1]\)</span> 、左上方状态 <span class="arithmatex">\(dp[i-1, j-1]\)</span> 转移而来,而正序遍历会丢失左上方 <span class="arithmatex">\(dp[i-1, j-1]\)</span> ,倒序遍历无法提前构建 <span class="arithmatex">\(dp[i, j-1]\)</span> ,因此两种遍历顺序都不可取。</p>
<p>由于 <span class="arithmatex">\(dp[i,j]\)</span> 是由上方 <span class="arithmatex">\(dp[i-1, j]\)</span>、左方 <span class="arithmatex">\(dp[i, j-1]\)</span>、左上方状态 <span class="arithmatex">\(dp[i-1, j-1]\)</span> 转移而来,而正序遍历会丢失左上方 <span class="arithmatex">\(dp[i-1, j-1]\)</span> ,倒序遍历无法提前构建 <span class="arithmatex">\(dp[i, j-1]\)</span> ,因此两种遍历顺序都不可取。</p>
<p>为此,我们可以使用一个变量 <code>leftup</code> 来暂存左上方的解 <span class="arithmatex">\(dp[i-1, j-1]\)</span> ,从而只需考虑左方和上方的解。此时的情况与完全背包问题相同,可使用正序遍历。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:12"><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" /><input id="__tabbed_3_12" 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">JS</label><label for="__tabbed_3_6">TS</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><label for="__tabbed_3_12">Rust</label></div>
<div class="tabbed-content">