This commit is contained in:
krahets
2023-08-17 05:12:16 +08:00
parent 2014338a92
commit 5884de5246
70 changed files with 1890 additions and 1219 deletions

View File

@ -3913,7 +3913,7 @@
<p>向以上代码输入数组 <span class="arithmatex">\([3, 4, 5]\)</span> 和目标元素 <span class="arithmatex">\(9\)</span> ,输出结果为 <span class="arithmatex">\([3, 3, 3], [4, 5], [5, 4]\)</span><strong>虽然成功找出了所有和为 <span class="arithmatex">\(9\)</span> 的子集,但其中存在重复的子集 <span class="arithmatex">\([4, 5]\)</span><span class="arithmatex">\([5, 4]\)</span></strong></p>
<p>这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如下图所示,先选 <span class="arithmatex">\(4\)</span> 后选 <span class="arithmatex">\(5\)</span> 与先选 <span class="arithmatex">\(5\)</span> 后选 <span class="arithmatex">\(4\)</span> 是两个不同的分支,但两者对应同一个子集。</p>
<p><img alt="子集搜索与越界剪枝" src="../subset_sum_problem.assets/subset_sum_i_naive.png" /></p>
<p align="center"> Fig. 子集搜索与越界剪枝 </p>
<p align="center"> 图:子集搜索与越界剪枝 </p>
<p>为了去除重复子集,<strong>一种直接的思路是对结果列表进行去重</strong>。但这个方法效率很低,因为:</p>
<ul>
@ -3933,7 +3933,7 @@
<li>若第一轮选择 <span class="arithmatex">\(5\)</span> <strong>则第二轮应该跳过 <span class="arithmatex">\(3\)</span><span class="arithmatex">\(4\)</span></strong> ,因为子集 <span class="arithmatex">\([5, 3, \cdots]\)</span> 和子集 <span class="arithmatex">\([5, 4, \cdots]\)</span><code>1.</code> , <code>2.</code> 中生成的子集完全重复。</li>
</ol>
<p><img alt="不同选择顺序导致的重复子集" src="../subset_sum_problem.assets/subset_sum_i_pruning.png" /></p>
<p align="center"> Fig. 不同选择顺序导致的重复子集 </p>
<p align="center"> 图:不同选择顺序导致的重复子集 </p>
<p>总结来看,给定输入数组 <span class="arithmatex">\([x_1, x_2, \cdots, x_n]\)</span> ,设搜索过程中的选择序列为 <span class="arithmatex">\([x_{i_1}, x_{i_2}, \cdots , x_{i_m}]\)</span> ,则该选择序列需要满足 <span class="arithmatex">\(i_1 \leq i_2 \leq \cdots \leq i_m\)</span> <strong>不满足该条件的选择序列都会造成重复,应当剪枝</strong></p>
<h3 id="_3">代码实现<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
@ -4364,7 +4364,7 @@
</div>
<p>如下图所示,为将数组 <span class="arithmatex">\([3, 4, 5]\)</span> 和目标元素 <span class="arithmatex">\(9\)</span> 输入到以上代码后的整体回溯过程。</p>
<p><img alt="子集和 I 回溯过程" src="../subset_sum_problem.assets/subset_sum_i.png" /></p>
<p align="center"> Fig. 子集和 I 回溯过程 </p>
<p align="center"> 图:子集和 I 回溯过程 </p>
<h2 id="1332">13.3.2. &nbsp; 考虑重复元素的情况<a class="headerlink" href="#1332" title="Permanent link">&para;</a></h2>
<div class="admonition question">
@ -4374,7 +4374,7 @@
<p>相比于上题,<strong>本题的输入数组可能包含重复元素</strong>,这引入了新的问题。例如,给定数组 <span class="arithmatex">\([4, \hat{4}, 5]\)</span> 和目标元素 <span class="arithmatex">\(9\)</span> ,则现有代码的输出结果为 <span class="arithmatex">\([4, 5], [\hat{4}, 5]\)</span> ,出现了重复子集。</p>
<p><strong>造成这种重复的原因是相等元素在某轮中被多次选择</strong>。如下图所示,第一轮共有三个选择,其中两个都为 <span class="arithmatex">\(4\)</span> ,会产生两个重复的搜索分支,从而输出重复子集;同理,第二轮的两个 <span class="arithmatex">\(4\)</span> 也会产生重复子集。</p>
<p><img alt="相等元素导致的重复子集" src="../subset_sum_problem.assets/subset_sum_ii_repeat.png" /></p>
<p align="center"> Fig. 相等元素导致的重复子集 </p>
<p align="center"> 图:相等元素导致的重复子集 </p>
<h3 id="_4">相等元素剪枝<a class="headerlink" href="#_4" title="Permanent link">&para;</a></h3>
<p>为解决此问题,<strong>我们需要限制相等元素在每一轮中只被选择一次</strong>。实现方式比较巧妙:由于数组是已排序的,因此相等元素都是相邻的。这意味着在某轮选择中,若当前元素与其左边元素相等,则说明它已经被选择过,因此直接跳过当前元素。</p>
@ -4856,7 +4856,7 @@
</div>
<p>下图展示了数组 <span class="arithmatex">\([4, 4, 5]\)</span> 和目标元素 <span class="arithmatex">\(9\)</span> 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。</p>
<p><img alt="子集和 II 回溯过程" src="../subset_sum_problem.assets/subset_sum_ii.png" /></p>
<p align="center"> Fig. 子集和 II 回溯过程 </p>
<p align="center"> 图:子集和 II 回溯过程 </p>