This commit is contained in:
krahets
2025-03-20 22:55:03 +08:00
parent 3b08169043
commit 546653f20d
20 changed files with 627 additions and 628 deletions

View File

@@ -2819,19 +2819,19 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1321-cases-without-equal-elements" class="md-nav__link">
<a href="#1321-cases-without-duplicate-elements" class="md-nav__link">
<span class="md-ellipsis">
13.2.1 &nbsp; Cases without equal elements
13.2.1 &nbsp; Cases without duplicate elements
</span>
</a>
<nav class="md-nav" aria-label="13.2.1   Cases without equal elements">
<nav class="md-nav" aria-label="13.2.1   Cases without duplicate elements">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-pruning-of-repeated-choices" class="md-nav__link">
<a href="#1-repeated-choice-pruning" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; Pruning of repeated choices
1. &nbsp; Repeated-choice pruning
</span>
</a>
@@ -2852,19 +2852,19 @@
</li>
<li class="md-nav__item">
<a href="#1322-considering-cases-with-equal-elements" class="md-nav__link">
<a href="#1322-considering-duplicate-elements" class="md-nav__link">
<span class="md-ellipsis">
13.2.2 &nbsp; Considering cases with equal elements
13.2.2 &nbsp; Considering duplicate elements
</span>
</a>
<nav class="md-nav" aria-label="13.2.2   Considering cases with equal elements">
<nav class="md-nav" aria-label="13.2.2   Considering duplicate elements">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-pruning-of-equal-elements" class="md-nav__link">
<a href="#1-equal-element-pruning" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; Pruning of equal elements
1. &nbsp; Equal-element pruning
</span>
</a>
@@ -2880,9 +2880,9 @@
</li>
<li class="md-nav__item">
<a href="#3-comparison-of-the-two-pruning-methods" class="md-nav__link">
<a href="#3-comparing-the-two-pruning-methods" class="md-nav__link">
<span class="md-ellipsis">
3. &nbsp; Comparison of the two pruning methods
3. &nbsp; Comparing the two pruning methods
</span>
</a>
@@ -3587,19 +3587,19 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1321-cases-without-equal-elements" class="md-nav__link">
<a href="#1321-cases-without-duplicate-elements" class="md-nav__link">
<span class="md-ellipsis">
13.2.1 &nbsp; Cases without equal elements
13.2.1 &nbsp; Cases without duplicate elements
</span>
</a>
<nav class="md-nav" aria-label="13.2.1   Cases without equal elements">
<nav class="md-nav" aria-label="13.2.1   Cases without duplicate elements">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-pruning-of-repeated-choices" class="md-nav__link">
<a href="#1-repeated-choice-pruning" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; Pruning of repeated choices
1. &nbsp; Repeated-choice pruning
</span>
</a>
@@ -3620,19 +3620,19 @@
</li>
<li class="md-nav__item">
<a href="#1322-considering-cases-with-equal-elements" class="md-nav__link">
<a href="#1322-considering-duplicate-elements" class="md-nav__link">
<span class="md-ellipsis">
13.2.2 &nbsp; Considering cases with equal elements
13.2.2 &nbsp; Considering duplicate elements
</span>
</a>
<nav class="md-nav" aria-label="13.2.2   Considering cases with equal elements">
<nav class="md-nav" aria-label="13.2.2   Considering duplicate elements">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-pruning-of-equal-elements" class="md-nav__link">
<a href="#1-equal-element-pruning" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; Pruning of equal elements
1. &nbsp; Equal-element pruning
</span>
</a>
@@ -3648,9 +3648,9 @@
</li>
<li class="md-nav__item">
<a href="#3-comparison-of-the-two-pruning-methods" class="md-nav__link">
<a href="#3-comparing-the-two-pruning-methods" class="md-nav__link">
<span class="md-ellipsis">
3. &nbsp; Comparison of the two pruning methods
3. &nbsp; Comparing the two pruning methods
</span>
</a>
@@ -3698,8 +3698,8 @@
<!-- Page content -->
<h1 id="132-permutation-problem">13.2 &nbsp; Permutation problem<a class="headerlink" href="#132-permutation-problem" title="Permanent link">&para;</a></h1>
<p>The permutation problem is a typical application of the backtracking algorithm. It is defined as finding all possible arrangements of elements from a given set (such as an array or string).</p>
<p>Table 13-2 lists several example data, including the input arrays and their corresponding permutations.</p>
<p>The permutation problem is a typical application of the backtracking algorithm. It involves finding all possible arrangements (permutations) of elements from a given set, such as an array or a string.</p>
<p>Table 13-2 shows several examples, including input arrays and their corresponding permutations.</p>
<p align="center"> Table 13-2 &nbsp; Permutation examples </p>
<div class="center-table">
@@ -3726,30 +3726,30 @@
</tbody>
</table>
</div>
<h2 id="1321-cases-without-equal-elements">13.2.1 &nbsp; Cases without equal elements<a class="headerlink" href="#1321-cases-without-equal-elements" title="Permanent link">&para;</a></h2>
<h2 id="1321-cases-without-duplicate-elements">13.2.1 &nbsp; Cases without duplicate elements<a class="headerlink" href="#1321-cases-without-duplicate-elements" title="Permanent link">&para;</a></h2>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>Enter an integer array without duplicate elements and return all possible permutations.</p>
<p>Given an integer array with no duplicate elements, return all possible permutations.</p>
</div>
<p>From the perspective of the backtracking algorithm, <strong>we can imagine the process of generating permutations as a series of choices</strong>. Suppose the input array is <span class="arithmatex">\([1, 2, 3]\)</span>, if we first choose <span class="arithmatex">\(1\)</span>, then <span class="arithmatex">\(3\)</span>, and finally <span class="arithmatex">\(2\)</span>, we obtain the permutation <span class="arithmatex">\([1, 3, 2]\)</span>. Backtracking means undoing a choice and then continuing to try other choices.</p>
<p>From the code perspective, the candidate set <code>choices</code> contains all elements of the input array, and the state <code>state</code> contains elements that have been selected so far. Please note that each element can only be chosen once, <strong>thus all elements in <code>state</code> must be unique</strong>.</p>
<p>As shown in Figure 13-5, we can unfold the search process into a recursive tree, where each node represents the current state <code>state</code>. Starting from the root node, after three rounds of choices, we reach the leaf nodes, each corresponding to a permutation.</p>
<p>From a backtracking perspective, <strong>we can view the process of generating permutations as a series of choices.</strong> Suppose the input array is <span class="arithmatex">\([1, 2, 3]\)</span>. If we choose <span class="arithmatex">\(1\)</span> first, then <span class="arithmatex">\(3\)</span>, and finally <span class="arithmatex">\(2\)</span>, we get the permutation <span class="arithmatex">\([1, 3, 2]\)</span>. "Backtracking" means undoing a previous choice and exploring alternative options.</p>
<p>From a coding perspective, the candidate set <code>choices</code> consists of all elements in the input array, while <code>state</code> holds the elements selected so far. Since each element can only be chosen once, <strong>all elements in <code>state</code> must be unique</strong>.</p>
<p>As illustrated in Figure 13-5, we can expand the search process into a recursive tree, where each node represents the current <code>state</code>. Starting from the root node, after three rounds of selections, we reach the leaf nodeseach corresponding to a permutation.</p>
<p><a class="glightbox" href="../permutations_problem.assets/permutations_i.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="Permutation recursive tree" class="animation-figure" src="../permutations_problem.assets/permutations_i.png" /></a></p>
<p align="center"> Figure 13-5 &nbsp; Permutation recursive tree </p>
<h3 id="1-pruning-of-repeated-choices">1. &nbsp; Pruning of repeated choices<a class="headerlink" href="#1-pruning-of-repeated-choices" title="Permanent link">&para;</a></h3>
<p>To ensure that each element is selected only once, we consider introducing a boolean array <code>selected</code>, where <code>selected[i]</code> indicates whether <code>choices[i]</code> has been selected. We base our pruning operations on this array:</p>
<h3 id="1-repeated-choice-pruning">1. &nbsp; Repeated-choice pruning<a class="headerlink" href="#1-repeated-choice-pruning" title="Permanent link">&para;</a></h3>
<p>To ensure each element is selected only once, we introduce a boolean array <code>selected</code>, where <code>selected[i]</code> indicates whether <code>choices[i]</code> has been chosen. We then base our pruning steps on this array:</p>
<ul>
<li>After making the choice <code>choice[i]</code>, we set <code>selected[i]</code> to <span class="arithmatex">\(\text{True}\)</span>, indicating it has been chosen.</li>
<li>When iterating through the choice list <code>choices</code>, skip all nodes that have already been selected, i.e., prune.</li>
<li>After choosing <code>choice[i]</code>, set <code>selected[i]</code> to <span class="arithmatex">\(\text{True}\)</span> to mark it as chosen.</li>
<li>While iterating through <code>choices</code>, skip all elements marked as chosen (i.e., prune those branches).</li>
</ul>
<p>As shown in Figure 13-6, suppose we choose 1 in the first round, 3 in the second round, and 2 in the third round, we need to prune the branch of element 1 in the second round and elements 1 and 3 in the third round.</p>
<p>As shown in Figure 13-6, suppose we choose 1 in the first round, then 3 in the second round, and finally 2 in the third round. We need to prune the branch for element 1 in the second round and the branches for elements 1 and 3 in the third round.</p>
<p><a class="glightbox" href="../permutations_problem.assets/permutations_i_pruning.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="Permutation pruning example" class="animation-figure" src="../permutations_problem.assets/permutations_i_pruning.png" /></a></p>
<p align="center"> Figure 13-6 &nbsp; Permutation pruning example </p>
<p>Observing Figure 13-6, this pruning operation reduces the search space size from <span class="arithmatex">\(O(n^n)\)</span> to <span class="arithmatex">\(O(n!)\)</span>.</p>
<p>From the figure, we can see that this pruning process reduces the search space from <span class="arithmatex">\(O(n^n)\)</span> to <span class="arithmatex">\(O(n!)\)</span>.</p>
<h3 id="2-code-implementation">2. &nbsp; Code implementation<a class="headerlink" href="#2-code-implementation" title="Permanent link">&para;</a></h3>
<p>After understanding the above information, we can "fill in the blanks" in the framework code. To shorten the overall code, we do not implement individual functions within the framework code separately, but expand them in the <code>backtrack()</code> function:</p>
<p>With this understanding, we can "fill in the blanks" of our framework code. To keep the overall code concise, we wont implement each part of the framework separately but instead expand everything in the <code>backtrack()</code> function:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:14"><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" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><input id="__tabbed_1_14" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label><label for="__tabbed_1_14">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -3917,26 +3917,26 @@
</div>
</div>
</div>
<h2 id="1322-considering-cases-with-equal-elements">13.2.2 &nbsp; Considering cases with equal elements<a class="headerlink" href="#1322-considering-cases-with-equal-elements" title="Permanent link">&para;</a></h2>
<h2 id="1322-considering-duplicate-elements">13.2.2 &nbsp; Considering duplicate elements<a class="headerlink" href="#1322-considering-duplicate-elements" title="Permanent link">&para;</a></h2>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>Enter an integer array, <strong>which may contain duplicate elements</strong>, and return all unique permutations.</p>
<p>Given an integer array**that may contain duplicate elements**, return all unique permutations.</p>
</div>
<p>Suppose the input array is <span class="arithmatex">\([1, 1, 2]\)</span>. To differentiate the two duplicate elements <span class="arithmatex">\(1\)</span>, we mark the second <span class="arithmatex">\(1\)</span> as <span class="arithmatex">\(\hat{1}\)</span>.</p>
<p>As shown in Figure 13-7, half of the permutations generated by the above method are duplicates.</p>
<p>Suppose the input array is <span class="arithmatex">\([1, 1, 2]\)</span>. To distinguish between the two identical elements <span class="arithmatex">\(1\)</span>, we label the second one as <span class="arithmatex">\(\hat{1}\)</span>.</p>
<p>As shown in Figure 13-7, half of the permutations produced by this method are duplicates:</p>
<p><a class="glightbox" href="../permutations_problem.assets/permutations_ii.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="Duplicate permutations" class="animation-figure" src="../permutations_problem.assets/permutations_ii.png" /></a></p>
<p align="center"> Figure 13-7 &nbsp; Duplicate permutations </p>
<p>So, how do we eliminate duplicate permutations? Most directly, consider using a hash set to deduplicate permutation results. However, this is not elegant, <strong>as branches generating duplicate permutations are unnecessary and should be identified and pruned in advance</strong>, which can further improve algorithm efficiency.</p>
<h3 id="1-pruning-of-equal-elements">1. &nbsp; Pruning of equal elements<a class="headerlink" href="#1-pruning-of-equal-elements" title="Permanent link">&para;</a></h3>
<p>Observing Figure 13-8, in the first round, choosing <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(\hat{1}\)</span> results in identical permutations under both choices, thus we should prune <span class="arithmatex">\(\hat{1}\)</span>.</p>
<p>Similarly, after choosing <span class="arithmatex">\(2\)</span> in the first round, choosing <span class="arithmatex">\(1\)</span> and <span class="arithmatex">\(\hat{1}\)</span> in the second round also produces duplicate branches, so we should also prune <span class="arithmatex">\(\hat{1}\)</span> in the second round.</p>
<p>Essentially, <strong>our goal is to ensure that multiple equal elements are only selected once in each round of choices</strong>.</p>
<p>So how can we eliminate these duplicate permutations? One direct approach is to use a hash set to remove duplicates after generating all permutations. However, this is less elegant <strong>because branches that produce duplicates are inherently unnecessary and should be pruned in advance,</strong> thus improving the algorithms efficiency.</p>
<h3 id="1-equal-element-pruning">1. &nbsp; Equal-element pruning<a class="headerlink" href="#1-equal-element-pruning" title="Permanent link">&para;</a></h3>
<p>Looking at Figure 13-8, in the first round, choosing <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(\hat{1}\)</span> leads to the same permutations, so we prune <span class="arithmatex">\(\hat{1}\)</span>.</p>
<p>Similarly, after choosing <span class="arithmatex">\(2\)</span> in the first round, choosing <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(\hat{1}\)</span> in the second round also leads to duplicate branches, so we prune <span class="arithmatex">\(\hat{1}\)</span> then as well.</p>
<p>Essentially, <strong>our goal is to ensure that multiple identical elements are only selected once per round of choices.</strong></p>
<p><a class="glightbox" href="../permutations_problem.assets/permutations_ii_pruning.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="Duplicate permutations pruning" class="animation-figure" src="../permutations_problem.assets/permutations_ii_pruning.png" /></a></p>
<p align="center"> Figure 13-8 &nbsp; Duplicate permutations pruning </p>
<h3 id="2-code-implementation_1">2. &nbsp; Code implementation<a class="headerlink" href="#2-code-implementation_1" title="Permanent link">&para;</a></h3>
<p>Based on the code from the previous problem, we consider initiating a hash set <code>duplicated</code> in each round of choices, used to record elements that have been tried in that round, and prune duplicate elements:</p>
<p>Based on the code from the previous problem, we introduce a hash set <code>duplicated</code> in each round. This set keeps track of elements we have already attempted, so we can prune duplicates:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:14"><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" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><input id="__tabbed_2_14" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Kotlin</label><label for="__tabbed_2_13">Ruby</label><label for="__tabbed_2_14">Zig</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4110,15 +4110,15 @@
</div>
</div>
</div>
<p>Assuming all elements are distinct from each other, there are <span class="arithmatex">\(n!\)</span> (factorial) permutations of <span class="arithmatex">\(n\)</span> elements; when recording results, it is necessary to copy a list of length <span class="arithmatex">\(n\)</span>, using <span class="arithmatex">\(O(n)\)</span> time. <strong>Thus, the time complexity is <span class="arithmatex">\(O(n!n)\)</span></strong>.</p>
<p>The maximum recursion depth is <span class="arithmatex">\(n\)</span>, using <span class="arithmatex">\(O(n)\)</span> frame space. <code>Selected</code> uses <span class="arithmatex">\(O(n)\)</span> space. At any one time, there can be up to <span class="arithmatex">\(n\)</span> <code>duplicated</code>, using <span class="arithmatex">\(O(n^2)\)</span> space. <strong>Therefore, the space complexity is <span class="arithmatex">\(O(n^2)\)</span></strong>.</p>
<h3 id="3-comparison-of-the-two-pruning-methods">3. &nbsp; Comparison of the two pruning methods<a class="headerlink" href="#3-comparison-of-the-two-pruning-methods" title="Permanent link">&para;</a></h3>
<p>Please note, although both <code>selected</code> and <code>duplicated</code> are used for pruning, their targets are different.</p>
<p>Assuming all elements are distinct, there are <span class="arithmatex">\(n!\)</span> (factorial) permutations of <span class="arithmatex">\(n\)</span> elements. Recording each result requires copying a list of length <span class="arithmatex">\(n\)</span>, which takes <span class="arithmatex">\(O(n)\)</span> time. <strong>Hence, the total time complexity is <span class="arithmatex">\(O(n!n)\)</span>.</strong></p>
<p>The maximum recursion depth is <span class="arithmatex">\(n\)</span>, using <span class="arithmatex">\(O(n)\)</span> stack space. The <code>selected</code> array also requires <span class="arithmatex">\(O(n)\)</span> space. Because there can be up to <span class="arithmatex">\(n\)</span> separate <code>duplicated</code> sets at any one time, they collectively occupy <span class="arithmatex">\(O(n^2)\)</span> space. <strong>Therefore, the space complexity is <span class="arithmatex">\(O(n^2)\)</span>.</strong></p>
<h3 id="3-comparing-the-two-pruning-methods">3. &nbsp; Comparing the two pruning methods<a class="headerlink" href="#3-comparing-the-two-pruning-methods" title="Permanent link">&para;</a></h3>
<p>Although both <code>selected</code> and <code>duplicated</code> serve as pruning mechanisms, they target different issues:</p>
<ul>
<li><strong>Repeated choice pruning</strong>: There is only one <code>selected</code> throughout the search process. It records which elements are currently in the state, aiming to prevent an element from appearing repeatedly in <code>state</code>.</li>
<li><strong>Equal element pruning</strong>: Each round of choices (each call to the <code>backtrack</code> function) contains a <code>duplicated</code>. It records which elements have been chosen in the current traversal (<code>for</code> loop), aiming to ensure equal elements are selected only once.</li>
<li><strong>Repeated-choice pruning</strong>(via <code>selected</code>): There is a single <code>selected</code> array for the entire search, indicating which elements are already in the current state. This prevents the same element from appearing more than once in <code>state</code>.</li>
<li><strong>Equal-element pruning</strong>(via <code>duplicated</code>): Each call to the <code>backtrack</code> function uses its own <code>duplicated</code> set, recording which elements have already been chosen in that specific iteration (<code>for</code> loop). This ensures that equal elements are selected only once per round of choices.</li>
</ul>
<p>Figure 13-9 shows the scope of the two pruning conditions. Note, each node in the tree represents a choice, and the nodes from the root to the leaf form a permutation.</p>
<p>Figure 13-9 shows the scope of these two pruning strategies. Each node in the tree represents a choice; the path from the root to any leaf corresponds to one complete permutation.</p>
<p><a class="glightbox" href="../permutations_problem.assets/permutations_ii_pruning_summary.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="Scope of the two pruning conditions" class="animation-figure" src="../permutations_problem.assets/permutations_ii_pruning_summary.png" /></a></p>
<p align="center"> Figure 13-9 &nbsp; Scope of the two pruning conditions </p>