This commit is contained in:
krahets
2023-05-10 19:38:29 +08:00
parent 2782a4e42b
commit f4fc7a8eeb
8 changed files with 555 additions and 361 deletions

View File

@@ -1892,16 +1892,16 @@
</thead>
<tbody>
<tr>
<td align="left">[1]</td>
<td align="left">[1]</td>
<td align="left"><span class="arithmatex">\([1]\)</span></td>
<td align="left"><span class="arithmatex">\([1]\)</span></td>
</tr>
<tr>
<td align="left">[1, 2]</td>
<td align="left">[1, 2], [2, 1]</td>
<td align="left"><span class="arithmatex">\([1, 2]\)</span></td>
<td align="left"><span class="arithmatex">\([1, 2], [2, 1]\)</span></td>
</tr>
<tr>
<td align="left">[1, 2, 3]</td>
<td align="left">[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]</td>
<td align="left"><span class="arithmatex">\([1, 2, 3]\)</span></td>
<td align="left"><span class="arithmatex">\([1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]\)</span></td>
</tr>
</tbody>
</table>
@@ -1910,7 +1910,7 @@
<div class="admonition question">
<p class="admonition-title">输入一个整数数组,数组中不包含重复元素,返回所有可能的排列。</p>
</div>
<p><strong>从回溯算法的角度看,我们可以把生成排列的过程想象成一系列选择的结果</strong>。假设输入数组为 <code>[1, 2, 3]</code> ,如果我们先选择 <code>1</code> 、再选择 <code>3</code> 、最后选择 <code>2</code> ,则获得排列 <code>[1, 3, 2]</code> 。回退表示撤销一个选择,之后继续尝试其他选择。</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>因此在遍历选择时,应当排除已经选择过的元素</strong></p>
<p>如下图所示,我们可以将搜索过程展开成一个递归树,树中的每个节点代表当前状态 <code>state</code> 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。</p>
<p><img alt="全排列的递归树" src="../permutations_problem.assets/permutations_i.png" /></p>
@@ -2124,12 +2124,12 @@
<div class="admonition question">
<p class="admonition-title">输入一个整数数组,<strong>数组中可能包含重复元素</strong>,返回所有不重复的排列。</p>
</div>
<p>假设输入数组为 <code>[1, 1, 2]</code> 。为了方便区分两个重复的元素 <code>1</code> ,接下来我们将第二个元素记为 <code>1'</code> 。如下图所示,上述方法生成的排列有一半都是重复的。</p>
<p>假设输入数组为 <span class="arithmatex">\([1, 1, 2]\)</span> 。为了方便区分两个重复的元素 <span class="arithmatex">\(1\)</span> ,接下来我们将第二个元素记为 <span class="arithmatex">\(\hat{1}\)</span> 。如下图所示,上述方法生成的排列有一半都是重复的。</p>
<p><img alt="重复排列" src="../permutations_problem.assets/permutations_ii.png" /></p>
<p align="center"> Fig. 重复排列 </p>
<p>那么,如何去除重复的排列呢?最直接地,我们可以借助一个哈希表,直接对排列结果进行去重。然而,这样做不够优雅,因为生成重复排列的搜索分支是没有必要的,应当被提前识别并剪枝,这样可以提升算法效率。</p>
<p>观察发现,在第一轮中,选择 <code>1</code> 或选择 <code>1'</code> 是等价的,因为在这两个选择之下生成的所有排列都是重复的。因此,我们应该把 <code>1'</code> 剪枝掉。同理,在第一轮选择 <code>2</code> 后,第二轮选择中的 <code>1</code><code>1'</code> 也会产生重复分支,因此也需要将第二轮的 <code>1'</code> 剪枝。</p>
<p>观察发现,在第一轮中,选择 <span class="arithmatex">\(1\)</span> 或选择 <span class="arithmatex">\(\hat{1}\)</span> 是等价的,因为在这两个选择之下生成的所有排列都是重复的。因此,我们应该把 <span class="arithmatex">\(\hat{1}\)</span> 剪枝掉。同理,在第一轮选择 <span class="arithmatex">\(2\)</span> 后,第二轮选择中的 <span class="arithmatex">\(1\)</span><span class="arithmatex">\(\hat{1}\)</span> 也会产生重复分支,因此也需要将第二轮的 <span class="arithmatex">\(\hat{1}\)</span> 剪枝。</p>
<p><img alt="重复排列剪枝" src="../permutations_problem.assets/permutations_ii_pruning.png" /></p>
<p align="center"> Fig. 重复排列剪枝 </p>