mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-24 20:16:06 +00:00
build
This commit is contained in:
@@ -240,7 +240,7 @@ For this problem, we perform a preorder traversal of the tree and check whether
|
||||
|
||||
**The reason it is called a backtracking algorithm is that it employs "attempt" and "backtrack" strategies when searching the solution space**. When the algorithm encounters a state where it cannot continue forward or cannot find a solution that satisfies the constraints, it will undo the previous choice, return to a previous state, and try other possible choices.
|
||||
|
||||
For Example 1, visiting each node represents an "attempt", while skipping over a leaf node or a function `return` from the parent node represents a "backtrack".
|
||||
For Example 1, visiting each node represents an "attempt", while skipping over a leaf node or the `return` that brings the traversal back to the parent node represents a "backtrack".
|
||||
|
||||
It is worth noting that **backtracking is not limited to function returns alone**. To illustrate this, let's extend Example 1 slightly.
|
||||
|
||||
@@ -248,7 +248,7 @@ It is worth noting that **backtracking is not limited to function returns alone*
|
||||
|
||||
In a binary tree, search all nodes with value $7$, **and return the paths from the root node to these nodes**.
|
||||
|
||||
Based on the code from Example 1, we need to use a list `path` to record the visited node path. When we reach a node with value $7$, we copy `path` and add it to the result list `res`. After traversal is complete, `res` contains all the solutions. The code is as follows:
|
||||
Based on the code from Example 1, we need to use a list `path` to record the path of visited nodes. When we reach a node with value $7$, we copy `path` and add it to the result list `res`. After traversal is complete, `res` contains all the solutions. The code is as follows:
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -895,7 +895,7 @@ To satisfy the above constraints, **we need to add pruning operations**: during
|
||||
|
||||
## 13.1.3 Framework Code
|
||||
|
||||
Next, we attempt to extract the main framework of backtracking's "attempt, backtrack, and pruning", to improve code generality.
|
||||
Next, we attempt to extract a general framework centered on backtracking's "attempt, backtrack, and pruning" to improve code generality.
|
||||
|
||||
In the following framework code, `state` represents the current state of the problem, and `choices` represents the choices available in the current state:
|
||||
|
||||
@@ -1915,7 +1915,7 @@ As per the problem statement, we should continue searching after finding a node
|
||||
|
||||
<p align="center"> Figure 13-4 Comparison of search process with and without return statement </p>
|
||||
|
||||
Compared to code based on preorder traversal, code based on the backtracking algorithm framework appears more verbose, but has better generality. In fact, **many backtracking problems can be solved within this framework**. We only need to define `state` and `choices` for the specific problem and implement each method in the framework.
|
||||
Compared to code based on preorder traversal, code based on the backtracking algorithm framework appears more verbose, but is more general. In fact, **many backtracking problems can be solved within this framework**. We only need to define `state` and `choices` for the specific problem and implement each method in the framework.
|
||||
|
||||
## 13.1.4 Common Terminology
|
||||
|
||||
@@ -1938,7 +1938,7 @@ To analyze algorithmic problems more clearly, we summarize the meanings of commo
|
||||
|
||||
!!! tip
|
||||
|
||||
The concepts of problem, solution, state, etc. are universal and are involved in divide-and-conquer, backtracking, dynamic programming, greedy and other algorithms.
|
||||
The concepts of problem, solution, state, etc. are universal and appear in divide-and-conquer, backtracking, dynamic programming, greedy algorithms, and others.
|
||||
|
||||
## 13.1.5 Advantages and Limitations
|
||||
|
||||
@@ -1946,7 +1946,7 @@ The backtracking algorithm is essentially a depth-first search algorithm that tr
|
||||
|
||||
However, when dealing with large-scale or complex problems, **the running efficiency of the backtracking algorithm may be unacceptable**.
|
||||
|
||||
- **Time**: The backtracking algorithm usually needs to traverse all possibilities in the solution space, and the time complexity can reach exponential or factorial order.
|
||||
- **Time**: The backtracking algorithm usually needs to traverse all possibilities in the state space, and the time complexity can reach exponential or factorial order.
|
||||
- **Space**: During recursive calls, the current state needs to be saved (such as paths, auxiliary variables used for pruning, etc.), and when the depth is large, the space requirement can become very large.
|
||||
|
||||
Nevertheless, **the backtracking algorithm is still the best solution for certain search problems and constraint satisfaction problems**. For these problems, since we cannot predict which choices will generate valid solutions, we must traverse all possible choices. In this case, **the key is how to optimize efficiency**. There are two common efficiency optimization methods.
|
||||
|
||||
@@ -6,7 +6,7 @@ comments: true
|
||||
|
||||
!!! question
|
||||
|
||||
According to the rules of chess, a queen can attack pieces that share the same row, column, or diagonal line. Given $n$ queens and an $n \times n$ chessboard, find a placement scheme such that no two queens can attack each other.
|
||||
According to the rules of chess, a queen can attack any piece in the same row, column, or diagonal. Given $n$ queens and an $n \times n$ chessboard, find an arrangement such that no two queens can attack each other.
|
||||
|
||||
As shown in Figure 13-15, when $n = 4$, there are two solutions that can be found. From the perspective of the backtracking algorithm, an $n \times n$ chessboard has $n^2$ squares, which provide all the choices `choices`. During the process of placing queens one by one, the chessboard state changes continuously, and the chessboard at each moment represents the state `state`.
|
||||
|
||||
@@ -22,11 +22,11 @@ Figure 13-16 illustrates the three constraints of this problem: **multiple queen
|
||||
|
||||
### 1. Row-By-Row Placement Strategy
|
||||
|
||||
Since both the number of queens and the number of rows on the chessboard are $n$, we can easily derive a conclusion: **each row of the chessboard allows and only allows exactly one queen to be placed**.
|
||||
Since both the number of queens and the number of rows on the chessboard are $n$, we can easily derive a conclusion: **each row of the chessboard allows one and only one queen to be placed**.
|
||||
|
||||
This means we can adopt a row-by-row placement strategy: starting from the first row, place one queen in each row until the last row is completed.
|
||||
|
||||
Figure 13-17 shows the row-by-row placement process for the 4-queens problem. Due to space limitations, the figure only expands one search branch of the first row, and all schemes that do not satisfy the column constraint and diagonal constraints are pruned.
|
||||
Figure 13-17 shows the row-by-row placement process for the 4-queens problem. Due to space limitations, the figure only expands one search branch of the first row, and all schemes that violate the column or diagonal constraints are pruned.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -54,7 +54,7 @@ Similarly, **for all squares on an anti-diagonal, the sum $row + col$ is a const
|
||||
|
||||
### 3. Code Implementation
|
||||
|
||||
Please note that in an $n$-dimensional square matrix, the range of $row - col$ is $[-n + 1, n - 1]$, and the range of $row + col$ is $[0, 2n - 2]$. Therefore, the number of both main diagonals and anti-diagonals is $2n - 1$, meaning the length of both arrays `diags1` and `diags2` is $2n - 1$.
|
||||
Please note that in an $n \times n$ square matrix, the range of $row - col$ is $[-n + 1, n - 1]$, and the range of $row + col$ is $[0, 2n - 2]$. Therefore, the number of both main diagonals and anti-diagonals is $2n - 1$, meaning the length of both arrays `diags1` and `diags2` is $2n - 1$.
|
||||
|
||||
=== "Python"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ As shown in Figure 13-5, we can unfold the search process into a recursion tree,
|
||||
|
||||
To ensure that each element is chosen only once, we consider introducing a boolean array `selected`, where `selected[i]` indicates whether `choices[i]` has been chosen. We implement the following pruning operation based on it.
|
||||
|
||||
- After making a choice `choice[i]`, we set `selected[i]` to $\text{True}$, indicating that it has been chosen.
|
||||
- After making a choice `choices[i]`, we set `selected[i]` to $\text{True}$, indicating that it has been chosen.
|
||||
- When traversing the candidate list `choices`, we skip all nodes that have been chosen, which is pruning.
|
||||
|
||||
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. Then we need to prune the branch of element $1$ in the second round and prune the branches of elements $1$ and $3$ in the third round.
|
||||
@@ -546,7 +546,7 @@ After understanding the above information, we can fill in the blanks in the temp
|
||||
|
||||
Suppose the input array is $[1, 1, 2]$. To distinguish the two duplicate elements $1$, we denote the second $1$ as $\hat{1}$.
|
||||
|
||||
As shown in Figure 13-7, the method described above generates permutations where half are duplicates.
|
||||
As shown in Figure 13-7, half of the permutations generated by the above method are duplicates.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -554,7 +554,7 @@ As shown in Figure 13-7, the method described above generates permutations where
|
||||
|
||||
So how do we remove duplicate permutations? The most direct approach is to use a hash set to directly deduplicate the permutation results. However, this is not elegant because **the search branches that generate duplicate permutations are unnecessary and should be identified and pruned early**, which can further improve algorithm efficiency.
|
||||
|
||||
### 1. Pruning Duplicate Elements
|
||||
### 1. Pruning Equal Elements
|
||||
|
||||
Observe Figure 13-8. In the first round, choosing $1$ or choosing $\hat{1}$ is equivalent. All permutations generated under these two choices are duplicates. Therefore, we should prune $\hat{1}$.
|
||||
|
||||
@@ -568,7 +568,7 @@ Essentially, **our goal is to ensure that multiple equal elements are chosen onl
|
||||
|
||||
### 2. Code Implementation
|
||||
|
||||
Building on the code from the previous problem, we consider opening a hash set `duplicated` in each round of choices to record which elements have been tried in this round, and prune duplicate elements:
|
||||
Building on the code from the previous problem, we initialize a hash set `duplicated` in each round of choices to record which elements have already been tried in that round, and prune equal elements:
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -1089,7 +1089,7 @@ The maximum recursion depth is $n$, using $O(n)$ stack frame space. `selected` u
|
||||
Note that although both `selected` and `duplicated` are used for pruning, they have different objectives.
|
||||
|
||||
- **Pruning duplicate choices**: There is only one `selected` throughout the entire search process. It records which elements are included in the current state, and its purpose is to prevent an element from appearing repeatedly in `state`.
|
||||
- **Pruning duplicate elements**: Each round of choices (each `backtrack` function call) contains a `duplicated` set. It records which elements have been chosen in this round's iteration (the `for` loop), and its purpose is to ensure that equal elements are chosen only once.
|
||||
- **Pruning equal elements**: Each round of choices (each `backtrack` function call) contains a `duplicated` set. It records which elements have been chosen in this round's iteration (the `for` loop), and its purpose is to ensure that equal elements are chosen only once.
|
||||
|
||||
Figure 13-9 shows the effective scope of the two pruning conditions. Note that each node in the tree represents a choice, and the nodes on the path from the root to a leaf node form a permutation.
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ For example, given the set $\{3, 4, 5\}$ and target integer $9$, the solutions a
|
||||
- Elements in the input set can be selected repeatedly without limit.
|
||||
- Subsets do not distinguish element order; for example, $\{4, 5\}$ and $\{5, 4\}$ are the same subset.
|
||||
|
||||
### 1. Reference to Full Permutation Solution
|
||||
### 1. Using the Permutation Solution as a Reference
|
||||
|
||||
Similar to the full permutation problem, we can imagine the process of generating subsets as a series of choices, and update the "sum of elements" in real-time during the selection process. When the sum equals `target`, we record the subset to the result list.
|
||||
Similar to the permutation problem, we can view the process of generating subsets as the result of a series of choices and update the running sum during the selection process. When the sum equals `target`, we record the subset in the result list.
|
||||
|
||||
Unlike the full permutation problem, **elements in this problem's set can be selected unlimited times**, so we do not need to use a `selected` boolean list to track whether an element has been selected. We can make minor modifications to the full permutation code and initially obtain the solution:
|
||||
Unlike the permutation problem, **elements in this problem can be selected any number of times**, so we do not need to use a `selected` boolean list to track whether an element has already been selected. With a few small changes to the permutation code, we obtain an initial solution:
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -501,7 +501,7 @@ Unlike the full permutation problem, **elements in this problem's set can be sel
|
||||
end
|
||||
```
|
||||
|
||||
When we input array $[3, 4, 5]$ and target element $9$ to the above code, the output is $[3, 3, 3], [4, 5], [5, 4]$. **Although we successfully find all subsets that sum to $9$, there are duplicate subsets $[4, 5]$ and $[5, 4]$**.
|
||||
Running the above code on array $[3, 4, 5]$ with target value $9$ produces $[3, 3, 3], [4, 5], [5, 4]$. **Although we successfully found all subsets that sum to $9$, there are duplicate subsets $[4, 5]$ and $[5, 4]$**.
|
||||
|
||||
This is because the search process distinguishes the order of selections, but subsets do not distinguish selection order. As shown in Figure 13-10, selecting 4 first and then 5 versus selecting 5 first and then 4 are different branches, but they correspond to the same subset.
|
||||
|
||||
@@ -519,13 +519,13 @@ To eliminate duplicate subsets, **one straightforward idea is to deduplicate the
|
||||
**We consider deduplication through pruning during the search process**. Observing Figure 13-11, duplicate subsets occur when array elements are selected in different orders, as in the following cases:
|
||||
|
||||
1. When the first and second rounds select $3$ and $4$ respectively, all subsets containing these two elements are generated, denoted as $[3, 4, \dots]$.
|
||||
2. Afterward, when the first round selects $4$, **the second round should skip $3$**, because the subset $[4, 3, \dots]$ generated by this choice is completely duplicate with the subset generated in step `1.`
|
||||
2. Afterward, when the first round selects $4$, **the second round should skip $3$**, because the subset $[4, 3, \dots]$ generated by this choice is an exact duplicate of the subset generated in step `1.`
|
||||
|
||||
In the search process, each level's choices are tried from left to right, so the rightmost branches are pruned more.
|
||||
|
||||
1. The first two rounds select $3$ and $5$, generating subset $[3, 5, \dots]$.
|
||||
2. The first two rounds select $4$ and $5$, generating subset $[4, 5, \dots]$.
|
||||
3. If the first round selects $5$, **the second round should skip $3$ and $4$**, because subsets $[5, 3, \dots]$ and $[5, 4, \dots]$ are completely duplicate with the subsets described in steps `1.` and `2.`
|
||||
3. If the first round selects $5$, **the second round should skip $3$ and $4$**, because subsets $[5, 3, \dots]$ and $[5, 4, \dots]$ are exact duplicates of the subsets described in steps `1.` and `2.`
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -1057,7 +1057,7 @@ In addition, we have made the following two optimizations to the code:
|
||||
end
|
||||
```
|
||||
|
||||
Figure 13-12 shows the complete backtracking process when array $[3, 4, 5]$ and target element $9$ are input to the above code.
|
||||
Figure 13-12 shows the complete backtracking process produced by running the above code on array $[3, 4, 5]$ with target value $9$.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
@@ -1069,7 +1069,7 @@ Figure 13-12 shows the complete backtracking process when array $[3, 4, 5]$ and
|
||||
|
||||
Given a positive integer array `nums` and a target positive integer `target`, find all possible combinations where the sum of elements in the combination equals `target`. **The given array may contain duplicate elements, and each element can be selected at most once**. Return these combinations in list form, where the list should not contain duplicate combinations.
|
||||
|
||||
Compared to the previous problem, **the input array in this problem may contain duplicate elements**, which introduces new challenges. For example, given array $[4, \hat{4}, 5]$ and target element $9$, the output of the existing code is $[4, 5], [\hat{4}, 5]$, which contains duplicate subsets.
|
||||
Compared to the previous problem, **the input array in this problem may contain duplicate elements**, which introduces a new issue. For example, given array $[4, \hat{4}, 5]$ and target value $9$, the output of the existing code is $[4, 5], [\hat{4}, 5]$, which contains duplicate subsets.
|
||||
|
||||
**The reason for this duplication is that equal elements are selected multiple times in a certain round**. In Figure 13-13, the first round has three choices, two of which are $4$, creating two duplicate search branches that output duplicate subsets. Similarly, the two $4$'s in the second round also produce duplicate subsets.
|
||||
|
||||
@@ -1079,7 +1079,7 @@ Compared to the previous problem, **the input array in this problem may contain
|
||||
|
||||
### 1. Pruning Equal Elements
|
||||
|
||||
To solve this problem, **we need to limit equal elements to be selected only once in each round**. The implementation is quite clever: since the array is already sorted, equal elements are adjacent. This means that in a certain round of selection, if the current element equals the element to its left, it means this element has already been selected, so we skip the current element directly.
|
||||
To solve this problem, **we need to limit equal elements to be selected only once in each round**. The implementation is quite clever: since the array is already sorted, equal elements are adjacent. This means that in a given round of selection, if the current element equals the element to its left, then the same value has already been chosen in this round, so we skip the current element directly.
|
||||
|
||||
At the same time, **this problem specifies that each array element can only be selected once**. Fortunately, we can also use the variable `start` to satisfy this constraint: after making choice $x_{i}$, set the next round to start traversal from index $i + 1$ onwards. This both eliminates duplicate subsets and avoids selecting elements multiple times.
|
||||
|
||||
@@ -1663,7 +1663,7 @@ At the same time, **this problem specifies that each array element can only be s
|
||||
end
|
||||
```
|
||||
|
||||
Figure 13-14 shows the backtracking process for array $[4, 4, 5]$ and target element $9$, which includes four types of pruning operations. Combine the illustration with the code comments to understand the entire search process and how each pruning operation works.
|
||||
Figure 13-14 shows the backtracking process for array $[4, 4, 5]$ with target value $9$, which includes four types of pruning operations. Combine the illustration with the code comments to understand the entire search process and how each pruning operation works.
|
||||
|
||||
{ class="animation-figure" }
|
||||
|
||||
|
||||
@@ -13,15 +13,15 @@ comments: true
|
||||
- The permutation problem aims to find all possible permutations of elements in a given set. We use an array to record whether each element has been selected, thereby pruning search branches that attempt to select the same element repeatedly, ensuring each element is selected exactly once.
|
||||
- In the permutation problem, if the set contains duplicate elements, the final result will contain duplicate permutations. We need to impose a constraint so that equal elements can only be selected once per round, which is typically achieved using a hash set.
|
||||
- The subset-sum problem aims to find all subsets of a given set that sum to a target value. Since the set is unordered but the search process outputs results in all orders, duplicate subsets are generated. We sort the data before backtracking and use a variable to indicate the starting point of each round's traversal, thereby pruning search branches that generate duplicate subsets.
|
||||
- For the subset-sum problem, equal elements in the array produce duplicate sets. We leverage the precondition that the array is sorted by checking whether adjacent elements are equal to implement pruning, ensuring that equal elements can only be selected once per round.
|
||||
- For the subset-sum problem, equal elements in the array produce duplicate subsets. We leverage the precondition that the array is sorted by checking whether adjacent elements are equal to implement pruning, ensuring that equal elements can only be selected once per round.
|
||||
- The $n$ queens problem aims to find placements of $n$ queens on an $n \times n$ chessboard such that no two queens can attack each other. The constraints of this problem include row constraints, column constraints, and main and anti-diagonal constraints. To satisfy row constraints, we adopt a row-by-row placement strategy, ensuring exactly one queen is placed in each row.
|
||||
- The handling of column constraints and diagonal constraints is similar. For column constraints, we use an array to record whether each column has a queen, thereby indicating whether a selected cell is valid. For diagonal constraints, we use two arrays to separately record whether queens exist on each main or anti-diagonal. The challenge lies in finding the row-column index pattern that characterizes cells on the same main (anti-)diagonal.
|
||||
|
||||
### 2. Q & A
|
||||
|
||||
**Q**: How should we understand the relationship between backtracking and recursion?
|
||||
**Q**: How can we understand the relationship between backtracking and recursion?
|
||||
|
||||
Overall, backtracking is an "algorithm strategy", while recursion is more like a "tool".
|
||||
Overall, backtracking is an algorithmic strategy, while recursion is better viewed as a tool.
|
||||
|
||||
- The backtracking algorithm is typically implemented based on recursion. However, backtracking is one application scenario of recursion and represents the application of recursion in search problems.
|
||||
- The structure of recursion embodies the "subproblem decomposition" problem-solving paradigm, commonly used to solve problems involving divide-and-conquer, backtracking, and dynamic programming (memoized recursion).
|
||||
- Backtracking is typically implemented with recursion. However, backtracking is only one application of recursion, specifically its use in search problems.
|
||||
- The structure of recursion reflects a problem-solving paradigm based on decomposing a problem into subproblems, and it is commonly used in divide-and-conquer, backtracking, and dynamic programming (memoized recursion).
|
||||
|
||||
Reference in New Issue
Block a user