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
@@ -2617,15 +2617,15 @@
<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>
@@ -2644,22 +2644,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="#_5" class="md-nav__link">
两种剪枝对比
<a href="#3" class="md-nav__link">
3. &nbsp; 两种剪枝对比
</a>
</li>
@@ -3430,15 +3430,15 @@
<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>
@@ -3457,22 +3457,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="#_5" class="md-nav__link">
两种剪枝对比
<a href="#3" class="md-nav__link">
3. &nbsp; 两种剪枝对比
</a>
</li>
@@ -3545,7 +3545,7 @@
<p><img alt="全排列的递归树" src="../permutations_problem.assets/permutations_i.png" /></p>
<p align="center"> 图:全排列的递归树 </p>
<h3 id="_1">重复选择剪枝<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h3>
<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>
<ul>
<li>在做出选择 <code>choice[i]</code> 后,我们就将 <code>selected[i]</code> 赋值为 <span class="arithmatex">\(\text{True}\)</span> ,代表它已被选择。</li>
@@ -3556,7 +3556,7 @@
<p align="center"> 图:全排列剪枝示例 </p>
<p>观察上图发现,该剪枝操作将搜索空间大小从 <span class="arithmatex">\(O(n^n)\)</span> 降低至 <span class="arithmatex">\(O(n!)\)</span></p>
<h3 id="_2">代码实现<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<h3 id="2">2. &nbsp; 代码实现<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<p>想清楚以上信息之后,我们就可以在框架代码中做“完形填空”了。为了缩短代码行数,我们不单独实现框架代码中的各个函数,而是将他们展开在 <code>backtrack()</code> 函数中。</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">
@@ -3967,14 +3967,14 @@
<p align="center"> 图:重复排列 </p>
<p>那么如何去除重复的排列呢?最直接地,考虑借助一个哈希表,直接对排列结果进行去重。然而这样做不够优雅,<strong>因为生成重复排列的搜索分支是没有必要的,应当被提前识别并剪枝</strong>,这样可以进一步提升算法效率。</p>
<h3 id="_3">相等元素剪枝<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
<h3 id="1_1">1. &nbsp; 相等元素剪枝<a class="headerlink" href="#1_1" title="Permanent link">&para;</a></h3>
<p>观察发现,在第一轮中,选择 <span class="arithmatex">\(1\)</span> 或选择 <span class="arithmatex">\(\hat{1}\)</span> 是等价的,在这两个选择之下生成的所有排列都是重复的。因此应该把 <span class="arithmatex">\(\hat{1}\)</span> 剪枝掉。</p>
<p>同理,在第一轮选择 <span class="arithmatex">\(2\)</span> 后,第二轮选择中的 <span class="arithmatex">\(1\)</span><span class="arithmatex">\(\hat{1}\)</span> 也会产生重复分支,因此也应将第二轮的 <span class="arithmatex">\(\hat{1}\)</span> 剪枝。</p>
<p>本质上看,<strong>我们的目标是在某一轮选择中,保证多个相等的元素仅被选择一次</strong></p>
<p><img alt="重复排列剪枝" src="../permutations_problem.assets/permutations_ii_pruning.png" /></p>
<p align="center"> 图:重复排列剪枝 </p>
<h3 id="_4">代码实现<a class="headerlink" href="#_4" 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>在上一题的代码的基础上,我们考虑在每一轮选择中开启一个哈希表 <code>duplicated</code> ,用于记录该轮中已经尝试过的元素,并将重复元素剪枝。</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:12"><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" /><input id="__tabbed_2_12" 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">JS</label><label for="__tabbed_2_6">TS</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><label for="__tabbed_2_12">Rust</label></div>
<div class="tabbed-content">
@@ -4354,7 +4354,7 @@
</div>
<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="_5">两种剪枝对比<a class="headerlink" href="#_5" title="Permanent link">&para;</a></h3>
<h3 id="3">3. &nbsp; 两种剪枝对比<a class="headerlink" href="#3" title="Permanent link">&para;</a></h3>
<p>请注意,虽然 <code>selected</code><code>duplicated</code> 都用作剪枝,但两者的目标不同:</p>
<ul>
<li><strong>重复选择剪枝</strong>:整个搜索过程中只有一个 <code>selected</code> 。它记录的是当前状态中包含哪些元素,作用是避免某个元素在 <code>state</code> 中重复出现。</li>