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

@@ -3569,19 +3569,19 @@
<p class="admonition-title">Question</p>
<p>输入一个整数数组,数组中不包含重复元素,返回所有可能的排列。</p>
</div>
<p>从回溯算法的角度看,<strong>我们可以把生成排列的过程想象成一系列选择的结果</strong>。假设输入数组为 <span class="arithmatex">\([1, 2, 3]\)</span> ,如果我们先选择 <span class="arithmatex">\(1\)</span> 、再选择 <span class="arithmatex">\(3\)</span> 、最后选择 <span class="arithmatex">\(2\)</span> ,则获得排列 <span class="arithmatex">\([1, 3, 2]\)</span> 。回退表示撤销一个选择,之后继续尝试其他选择。</p>
<p>从回溯算法的角度看,<strong>我们可以把生成排列的过程想象成一系列选择的结果</strong>。假设输入数组为 <span class="arithmatex">\([1, 2, 3]\)</span> ,如果我们先选择 <span class="arithmatex">\(1\)</span>、再选择 <span class="arithmatex">\(3\)</span>、最后选择 <span class="arithmatex">\(2\)</span> ,则获得排列 <span class="arithmatex">\([1, 3, 2]\)</span> 。回退表示撤销一个选择,之后继续尝试其他选择。</p>
<p>从回溯代码的角度看,候选集合 <code>choices</code> 是输入数组中的所有元素,状态 <code>state</code> 是直至目前已被选择的元素。请注意,每个元素只允许被选择一次,<strong>因此 <code>state</code> 中的所有元素都应该是唯一的</strong></p>
<p>如图 13-5 所示,我们可以将搜索过程展开成一个递归树,树中的每个节点代表当前状态 <code>state</code> 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。</p>
<p><img alt="全排列的递归树" src="../permutations_problem.assets/permutations_i.png" /></p>
<p align="center"> 图 13-5 &nbsp; 全排列的递归树 </p>
<h3 id="1">1. &nbsp; 重复选择剪枝<a class="headerlink" href="#1" title="Permanent link">&para;</a></h3>
<p>为了实现每个元素只被选择一次,我们考虑引入一个布尔型数组 <code>selected</code> ,其中 <code>selected[i]</code> 表示 <code>choices[i]</code> 是否已被选择。剪枝的实现原理为:</p>
<p>为了实现每个元素只被选择一次,我们考虑引入一个布尔型数组 <code>selected</code> ,其中 <code>selected[i]</code> 表示 <code>choices[i]</code> 是否已被选择,并基于它实现以下剪枝操作。</p>
<ul>
<li>在做出选择 <code>choice[i]</code> 后,我们就将 <code>selected[i]</code> 赋值为 <span class="arithmatex">\(\text{True}\)</span> ,代表它已被选择。</li>
<li>遍历选择列表 <code>choices</code> 时,跳过所有已被选择过的节点,即剪枝。</li>
</ul>
<p>如图 13-6 所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1, 3 的分支。</p>
<p>如图 13-6 所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1 和元素 3 的分支。</p>
<p><img alt="全排列剪枝示例" src="../permutations_problem.assets/permutations_i_pruning.png" /></p>
<p align="center"> 图 13-6 &nbsp; 全排列剪枝示例 </p>
@@ -4385,7 +4385,7 @@
<p>假设元素两两之间互不相同,则 <span class="arithmatex">\(n\)</span> 个元素共有 <span class="arithmatex">\(n!\)</span> 种排列(阶乘);在记录结果时,需要复制长度为 <span class="arithmatex">\(n\)</span> 的列表,使用 <span class="arithmatex">\(O(n)\)</span> 时间。因此,<strong>时间复杂度为 <span class="arithmatex">\(O(n!n)\)</span></strong></p>
<p>最大递归深度为 <span class="arithmatex">\(n\)</span> ,使用 <span class="arithmatex">\(O(n)\)</span> 栈帧空间。<code>selected</code> 使用 <span class="arithmatex">\(O(n)\)</span> 空间。同一时刻最多共有 <span class="arithmatex">\(n\)</span><code>duplicated</code> ,使用 <span class="arithmatex">\(O(n^2)\)</span> 空间。<strong>因此空间复杂度为 <span class="arithmatex">\(O(n^2)\)</span></strong></p>
<h3 id="3">3. &nbsp; 两种剪枝对比<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>请注意,虽然 <code>selected</code><code>duplicated</code> 都用作剪枝,但两者的目标不同</p>
<p>请注意,虽然 <code>selected</code><code>duplicated</code> 都用作剪枝,但两者的目标不同的。</p>
<ul>
<li><strong>重复选择剪枝</strong>:整个搜索过程中只有一个 <code>selected</code> 。它记录的是当前状态中包含哪些元素,作用是避免某个元素在 <code>state</code> 中重复出现。</li>
<li><strong>相等元素剪枝</strong>:每轮选择(即每个开启的 <code>backtrack</code> 函数)都包含一个 <code>duplicated</code> 。它记录的是在遍历中哪些元素已被选择过,作用是保证相等元素只被选择一次。</li>