1. Backtracking algorithm
1.1 What is backtracking?
Backtracking algorithm is actually a search attempt process similar to enumeration, which is mainly to find the solution of the problem in the search attempt process. When it is found that the solution condition is not met, it will “backtrack” back and try another path. — From Baidu Encyclopedia
1.1 General Steps:
- For the given problem, the solution space of the problem is defined, which contains at least one (optimal) solution of the problem.
- The structure of the solution space is determined so that the whole solution space can be easily searched by backtracking.
- The solution space is searched depth-first, and the pruning function is used to avoid invalid searches.
1.2 How to understand backtracking algorithm?
- Construct solution space structure for the problem
- DFS search is performed on the structure of solution space
- Set up backtracking exit and prune point, reduce invalid search, save effective solution at exit.
1.3 Which problems are solved?
- Combination problem: N numbers in accordance with certain rules to find the set of k numbers
- Slicing problem: A string can be sliced in several ways according to certain rules
- Subset problem: how many subsets of a set of N numbers are eligible
- Permutation problem: N number permutation according to certain rules, there are several permutations
- Checkerboard problems: N queen, solving sudoku and so on.
1.4 Recursion and backtracking
First, explain the understanding of recursion and Backtrack.
1.4.1 Recursive (Recursive)
The technique by which a program calls itself is called recursion. Recursion as an algorithm is widely used in programming languages. A procedure or function in the introduction to the definition or call their own, directly or indirectly, with a kind of method, it is usually the problem of a large complex layers into a similar to the original problem of smaller problems to solve, the recursion strategy only a small number of procedures can be required to describe the problem solving process of repeatedly calculation, greatly reduces the amount of program code. — From Baidu Encyclopedia
In general, in order to describe a state of the problem, one must use the state above that state; And if you want to describe the previous state, you have to use the previous state of the previous state… The way you define yourself in terms of yourself is recursion.
1.4.2. Back (Backtrack)
Backtracking algorithm is actually a search attempt process similar to enumeration, which is mainly to find the solution of the problem in the search attempt process. When it is found that the solution condition is not met, it will “backtrack” back and try another path. — From Baidu Encyclopedia
In this way of thinking, we need to identify clearly three factors: Options, Restraints, and conditions of Termination.
1.5. Difference between recursion and backtracking
Recursion is an algorithmic structure. Recursion occurs in subroutines in the form of directly or indirectly calling itself. The classic example is factorial, which is calculated as n! = n * (n – 1)! n! =n \times (n-1)! , basically as follows:
let fac = (n) = > {
if(n == 1) {return n;
}else{
return (n*fac(n - 1)); }}Copy the code
Backtracking is an algorithmic idea that is implemented recursively. The process of backtracking is similar to the exhaustive method, but it has the function of “pruning”, that is, the process of self-judgment.
Second, Leetcode backtracking
2.1-22. Parenthesis generation
The number n represents the logarithm of parentheses generated. Design a function that generates all possible and valid parentheses combinations.
Example 1:
Enter n =3Output:"((()))"."() () ()"."() () ()"."() () ()"."() () ()"]
Copy the code
Example 2:
Enter n =1Output:"()"]
Copy the code
Tip: 1 <= n <= 8
Thought analysis
- The number of left parentheses is n at the beginning; When the left parenthesis (() is left, proceed with the selection;
- Select the close parenthesis when there is more left than the open parenthesis; Keep recursively making choices
- Exit: when the string to build is 2n, at this time the branch has been built, add option;
Draw a graph
The problem solving code
var generateParenthesis = function (n) {
const res = [];
const backTracing = (lRemain, rRemain, str) = > { // The number of left parentheses, STR is the currently constructed string
if (str.length == 2 * n) { // String construction is complete
res.push(str); // Add the solution set
return; // End the current recursive branch
}
if (lRemain > 0) { // As long as the left parenthesis is left, you can select it and continue with the selection (recursion)
backTracing(lRemain - 1, rRemain, str + "(");
}
if (lRemain < rRemain) { // Select the right parenthesis if there is more left than left parenthesis
backTracing(lRemain, rRemain - 1, str + ")"); // Then proceed with the selection (recursion)}}; backTracing(n, n,""); // Recursive entry, the remaining number is n, the initial string is an empty string
return res;
};
Copy the code
2.2-46. All permutations
Given an array nums that does not contain duplicate digits, return all possible permutations. You can return the answers in any order.
Example 1:
Enter: nums = [1.2.3] Output: [[1.2.3], [1.3.2], [2.1.3], [2.3.1], [3.1.2], [3.2.1]]
Copy the code
Example 2:
Enter: nums = [0.1] Output: [[0.1], [1.0]]
Copy the code
Example 3:
Enter: nums = [1] Output: [[1]]
Copy the code
1 <= nums.length <= 6-10 <= nums[I] <= 10
Their thinking
- Backtracking termination conditions: the path length and reach nums length;
- Add the current value to the path, if the result already contains the path, do not add to the result, otherwise continue to select this option;
The problem solving code
/ * * *@param {number[]} nums
* @return {number[][]}* /
var permute = function (nums) {
if(! nums.length)return
let res = []
let backTrack = path= > {
// If the length meets the condition, add the result
if (path.length === nums.length) {
res.push(path)
return
}
nums.forEach(item= > {
if (path.includes(item)) return // Does not contain duplicate numbers
backTrack([...path, item]) // Add path, continue recursive selection
});
}
backTrack([])
return res
};
Copy the code
2.3-N Queen problem
The study is how to place n queens on an n by n chessboard so that queens cannot attack each other.
Given an integer n, return the number of different solutions to the queen of N problem. * * *
Rule of queen’s move
The queen’s way of walking is: can walk horizontally and diagonally, the number of grids is not limited. So asking queens not to attack each other is equivalent to asking no two queens to be on the same row, column, and slash.
The sample
Example 1:
Enter n =4Output:2
Copy the code
Explanation: As shown in the figure above, there are two different solutions to the four queens problem.
Example 2:
Enter n =1Output:1
Copy the code
Tip: 1 <= n <= 9
Their thinking
- Define a test function to determine the current position, with constraints such as, not in a row, not in a column, not in a diagonal (45 degrees and 135 degrees)
- Define checkerboard; Standard backtracking;
Backtracking is used by placing a queen in each row in turn, with each newly placed queen having no attacks on the already placed queen, i.e., the newly placed queen cannot be in the same column and on the same slash as any already placed queen. When NNN queens are all placed, a possible solution is found and the number of possible solutions is added to 111.
photo
The problem solving code
var totalNQueens = function (n) {
let count = 0; // Total number of queens that can be placed
let isValid = (row, col, board, n) = > {
// It moves down one line at a time
// Check whether the same column contains data
for (let i = 0; i < row; i++) {
if (board[i][col] === 'Q') {
return false}}// Check whether the 45 degree diagonal is included
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] === 'Q') {
return false}}// Check whether the 135 degree diagonal contains
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; j--, i--) {
if (board[i][j] === 'Q') {
return false}}return true
}
let backTracing = (row, board) = > {
// Go to the last row, count the number of times
if (row === n) {
count++;
return
}
for (let x = 0; x < n; x++) {
// Determine if this position is suitable for the queen
if (isValid(row, x, board, n)) {
board[row][x] = 'Q'; // Place the queen
backTracing(row + 1, board); / / recursion
board[row][x] = '. '; // Backtrace, undo the result of processing
}
}
}
backTracing(0, board)
let board = [...Array(n)].map(v= > v = ([...Array(n)]).fill('. ')) / / board
return count
};
Copy the code
2.4-78. Subsets
You are given an array of integers, nums, whose elements are different from each other. Returns all possible subsets (power sets) of this array.
The solution set cannot contain duplicate subsets. You can return the solution set in any order.
Example 1:
Enter: nums = [1.2.3] output: [[],[1], [2], [1.2], [3], [1.3], [2.3], [1.2.3]]
Copy the code
Example 2:
Enter: nums = [0] output: [[],[0]]
Copy the code
1 <= nums.length <= 10-10 <= nums[I] <= 10 all elements in nums are different
Their thinking
- Enumerates all optional numbers; Join option;
- Undoes the added option and adds the option to the result
The problem solving code
const subsets = (nums) = > {
const res = [];
const backTracing = (index, list) = > {
res.push(list.slice()); // Add the solution set before calling the subrecursion
for (let i = index; i < nums.length; i++) { // Enumerate all the optional numbers
list.push(nums[i]); // Select this number
backTracing(i + 1, list); // Continue recursion based on the number chosen
list.pop(); // Undo the selection}}; backTracing(0[]);return res;
};
Copy the code
2.5-77. Combinations
Given two integers n and k, return all possible combinations of k numbers in the range [1, n].
You can return the answers in any order.
Example 1:
Enter n =4, k = 2Output: [[2.4],
[3.4],
[2.3],
[1.2],
[1.3],
[1.4]]Copy the code
Example 2:
Enter n =1, k = 1Output: [[1]]
Copy the code
1 <= n <= 20 1 <= k <= n
Their thinking
- Enumerates all optional numbers; Join option;
- Undoes the added option and adds the option to the result
- Pruning condition: The length of the option meets the condition;
The problem solving code
/ * * *@param {number} n
* @param {number} k
* @return {number[][]}* /
var combine = function (n, k) {
let result = [];
let backTracing = (start, path) = > {
// If the selection is full, add it to the result set
if (path.length == k) {
result.push(path.slice());
return;
}
// From the beginning digit to the end digit
for (let i = start; i <= n; i++) {
// Add to the selection list
path.push(i);
// Continue the deep search
backTracing(i + 1, path);
// Undo the selectionpath.pop(i); }}; backTracing(1[]);return result;
};
Copy the code
2.6-081. Allow repeated selection of combinations of elements
Given an array of positive integer candidates with no duplicate elements and a positive integer target, find all unique combinations of the candidates that can make the sum the target number.
The numbers in the candidates can be selected repeatedly without limit. Both combinations are unique if at least one of the selected numbers has a different number. For a given input, there are fewer than 150 unique combinations of guaranteed and targets.
Example 1:
Enter: candidates = [2.3.6.7], target = 7Output: [[7], [2.2.3]]
Copy the code
Example 2:
Enter: candidates = [2.3.5], target = 8Output: [[2.2.2.2], [2.3.3], [3.5]]
Copy the code
Example 3:
Enter: candidates = [2], target = 1Output: []Copy the code
Example 4:
Enter: candidates = [1], target = 1Output: [[1]]
Copy the code
Example 5:
Enter: candidates = [1], target = 2Output: [[1.1]]
Copy the code
Description: 1 <= candidates.length <= 30 1 <= candidates.length [I] <= 200 Each element of the candidate is unique. 1 <= target <= 500
Their thinking
- Add the current element to the option, and pass the calculation result to the option, continue the recursion;
- When an item is selected and greater than the target value, end the option until a matching option is found and added to the result;
The problem solving code
var combinationSum = function (candidates, target) {
const result = [], visited = [];
backTracing(0.0);
return result;
function backTracing(sum, cur) {
if (target === sum) result.push(visited.slice());
if (target <= sum) return;
for (let i = cur; i < candidates.length; i++) {
visited.push(candidates[i]); // Add it to the options
backTracing(sum + candidates[i], i);// Select this option to continue the recursion
visited.pop(); // Insert this selection}}};Copy the code
2.7-216. Sum of combinations III
Find all the combinations of k numbers that add up to n. Only positive integers from 1 to 9 are allowed in a combination, and there are no duplicate digits in each combination.
Note: All numbers are positive integers. The solution set cannot contain repeated combinations.
Example 1:
Enter k =3, n = 7Output: [[1.2.4]]
Copy the code
Example 2:
Enter k =3, n = 9Output: [[1.2.6], [1.3.5], [2.3.4]]
Copy the code
Their thinking
With the combination of 1
The problem solving code
var combinationSum3 = function (k, n) {
let ans = [];
let backTracing = (start, path) = > {
if (path.length === k && path.reduce((acc, prev) = > acc += prev) === n) {
ans.push(path.slice())
return
}
for (let i = start; i <= 9; i++) {
path.push(i)
backTracing(i + 1, path)
path.pop(i)
}
}
backTracing(1[]),return ans
};
Copy the code
2.8-17. Alphabetic combinations of telephone numbers
Given a string containing only the numbers 2-9, return all the letter combinations it can represent. The answers can be returned in any order.
The mapping of numbers to letters is given as follows (same as a telephone key). Note that 1 does not correspond to any letter.
Example 1:
Enter: digits ="23"Output:"ad"."ae"."af"."bd"."be"."bf"."cd"."ce"."cf"]
Copy the code
Example 2:
Enter: digits =""Output: []Copy the code
Example 3:
Enter: digits ="2"Output:"a"."b"."c"]
Copy the code
Tip: 0 <= digits. Length <= 4 digits[I] is a number in the range [‘2’, ‘9’].
Their thinking
- Finds the alphabetic string for the current button
- Concatenate the string combination corresponding to the button
- If the option satisfies the length, add the result
The problem solving code
var letterCombinations = function (digits) {
if(! digits.length)return []
const dic = {
2: 'abc'.3: 'def'.4: 'ghi'.5: 'jkl'.6: 'mno'.7: 'pqrs'.8: 'tuv'.9: 'wxyz',
}, ans = [];
let backTracing = (cur, index) = > {
if (index > digits.length - 1) { // If the option meets the length, add the result
ans.push(cur)
return
}
let curDic = dic[digits[index]] // Find the letter string corresponding to the current button
for (let prop of curDic) {
backTracing(cur + prop,index+1) // Concatenate the string combination corresponding to the button
}
}
backTracing(' '.0)
return ans
};
Copy the code
2.9-08.01. Three-step problem
Three-step problem. A child is walking up a staircase. The staircase has n steps, and the child can walk up one, two or three at a time. Implement a method that counts the number of ways a child can walk up stairs. The result may be large and you need to set the result module to 1000000007.
Example 1:
Enter n =3Output:4Explanation: There are four ways to goCopy the code
Example 2:
Enter n =5Output:13
Copy the code
Note: n ranges from [1, 1000000]
Solution code (will time out)
var waysToStep = function (n) {
let ans = [], map = [1.2.3]
let backTracing = (path, sum) = > {
if (sum >= n) {
if (sum == n) {
ans++;
}
return
}
for (let i = 0; i < 3; i++) {
path.push(map[i]);
backTracing(path, sum + map[i])
path.pop(i)
}
}
backTracing([], 0)
return ans
};
Copy the code
Dynamic programming solution
/ * * *@param {number} n
* @return {number}* /
var waysToStep = function (n) {
let dp =[],mod = 1000000007;
dp[0] =0,dp[1] =1,dp[2] =2,dp[3] =4;
for (let i = 4; i <= n; i++) {
dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 3]) % mod
}
return dp[n]
};
Copy the code
2-10-40. Sum of combinations II
Given an array of candidates and a target number target, find all combinations of the candidates that can be the sum of the number and target.
Each number in the candidates must be used only once in each combination. Note: The solution set cannot contain repeated combinations.
Example 1:
Enter: candidates = [10.1.2.7.6.1.5], target = 8, output: [[1.1.6],
[1.2.5],
[1.7],
[2.6]]Copy the code
Example 2:
Enter: candidates = [2.5.2.1.2], target = 5, output: [[1.2.2],
[5]]Copy the code
1 <= candidate. length <= 100 1 <= candidate. length <= 50 1 <= target <= 30
Their thinking
The same idea as combination 1
The problem solving code
/ * * *@param {number[]} candidates
* @param {number} target
* @return {number[][]}* /
var combinationSum2 = function (candidates, target) {
candidates.sort((a,b) = >a-b)
let ans = [];
let backTracing = (start, path, sum) = > {
if (sum >= target) {
if (sum === target) {
ans.push(path.slice())
}
return
}
for (let i = start; i < candidates.length; i++) {
if (i - 1 >= start && candidates[i - 1] == candidates[i]) {
continue;
}
path.push(candidates[i])
backTracing(i + 1, path, sum + candidates[i])
path.pop()
}
}
backTracing(0[],0)
return ans
};
Copy the code
2-11-47. Full array II
Given a sequence nums that can contain repeating digits, return all non-repeating full permutations in any order.
Example 1:
Enter: nums = [1.1.2] Output: [[1.1.2],
[1.2.1],
[2.1.1]]
Copy the code
Example 2:
Enter: nums = [1.2.3] Output: [[1.2.3], [1.3.2], [2.1.3], [2.3.1], [3.1.2], [3.2.1]]
Copy the code
1 <= nums.length <= 8-10 <= nums[I] <= 10
Their thinking
Same as above
The problem solving code
var permuteUnique = function (nums) {
let ans = [];
let used = Array(nums.length).fill(false)
let backTracing = (start, path) = > {
if (start === nums.length) {
ans.push(path.slice())
return
}
for (let i = 0; i < nums.length; ++i) {
if (used[i] || (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {continue;
}
path.push(nums[i])
used[i] = true
backTracing(start + 1, path)
used[i] = false
path.pop()
}
}
nums.sort((a, b) = > a - b)
backTracing(0[]),return ans
};
Copy the code
Third, summary
The backtracking algorithm is mainly used. To solve a backtracking problem is actually a decision tree traversal process.
3.1 template
let backtracking=(Path, select list) = >{
if(End condition satisfied) {store path;return;
}
for(Select: path, select list) {make a selection; Backtracking (path, select list);/ / recursionBacktrace, undo processing result}}Copy the code
That is:
- 1. Path: The choices already made.
- 2. Selection list: The choices you can currently make.
- 3. End condition: that is, the condition when you reach the bottom of the decision tree and can no longer make a choice.
3.2 Pruning function
- 1. Use constraints to cut off the subtree of viable solutions that cannot be obtained
- 2. Use the objective function to cut the subtree of the optimal solution that cannot be obtained
3.3 General steps of retrospective method:
- 1. Set the initialization scheme (assign initial values to variables, read known data, etc.)
- 2. Change the way to test. If all the tests are finished, turn to the side (7)
- 3. Judge whether this method is successful (by constraint function), if not, go to (2)
- 4. Try again before you succeed
- 5. If the correct scheme is not found, go to (2)
- 6. To find a solution to record and print
- 7. Step back (backtrack), if not back to the end, turn (2)
- 8. If it has retreated to the end, it will end or print no solution
Keep going!!
Iv. References
-
LeetCode notes — Understanding recursion and backtracking
-
Graphical backtracking algorithm graphical backtracking algorithm
-
Summary of the backtracking algorithm Summary of the backtracking algorithm