I am participating in Nuggets Creators Training Camp # 4 (link: juejin.cn/post/706419…
Preface ✨
As a student who changed his major to 🥧, and because of the epidemic in the second semester of the freshman year, online courses at home are basically in the state of playing 🐟 for three days and drying for two days. Has always been a bit algorithmic (unknown fear). In the second semester of my sophomore year, I did not brush the algorithm until September last year. I have always used JS as the first language to learn algorithms, and I have gained a lot after one or two months of algorithm problem brushing. Maybe I have become more confident, and EVEN though SOME problems may not be solved for a while, I am still willing to settle down and quietly appreciate (learn from) a piece of code every day. Sometimes I have to sigh that good code will be as beautiful as poetry, and after reading some great god’s solution, I will have a feeling of enlightenment.
Solving a moderately difficult algorithmic problem is like climbing a mountain, and often it gives me more satisfaction than just the +1 I put into solving the problem. This process can be a lot more rewarding than we thought. ✨
Experience sharing
First of all, I am not a god. I want to share this article with others who are learning algorithms. Front-end learning is basically sticking together to warm up, learning and imitating is our innate ability. The author, as a beneficiary of the platform, now shares some of my personal experience with you, hoping it can be helpful to you.
The data of this paper came from CodeTop enterprise question bank
Emphasis:
- More drawing
- More drawing
- More drawing
How to face their own problems, direct problem solving is sometimes more difficult to understand, the problem by drawing the way ✍ down, will let you do primary school mathematics in general 📕 to find rules, this habit is not only suitable for the retrospective algorithm introduced in this article. So stubborn friend happy 😎 must develop the habit of drawing!
(1) Backtracking to solve the problem ✨
1.1 Introduction to backtracking algorithm
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. Backtracking is a kind of optimal search method, which searches forward according to the optimal conditions to reach the target. However, when a certain step is explored, it is found that the original choice is not good or fails to reach the target, so it takes a step back to make a new choice. The technology of going back and going again when there is no way is called backtracking method, and the point in a certain state that meets the backtracking condition is called “backtracking point”. Backtracking can be used for many complex, large-scale problems, and has the good name of being a “general solution.” — Baidu Encyclopedia
The essence of backtracking algorithm is a violent search process.
From a road to go forward, can enter into, can not enter back, another road to try again. An algorithm that doesn’t run into a wall
1.2 Main problems solved by backtracking algorithm
- Combination problem:
Find the set of k numbers in N numbers according to certain rules
- Permutation problem:
N numbers are arranged according to certain rules, and there are several ways to arrange them
- Cutting problems:
A string can be cut in several ways according to certain rules
- Subset problem:
How many subsets are there in a set of N numbers
- Checkerboard problem:
N Queen, solving sudoku and so on
1.3 Template of backtracking algorithm
The significance of the framework is to help us quickly set up the steps to solve the problem and reduce the situation that we have ideas but do not know how to start. Backtracking algorithms also have their own framework.
- The backtracking algorithm can basically be viewed as an n-fork tree, with the horizontal axis representing the number of elements selected and the vertical axis representing the recursive process.
function backtrak(parameter){
if(Recursive termination condition){// result.push() adds result set
return
}
for(elementinSet){make a selection backtrack(current selection result) undo the selection// This step is the backtracking operation}}Copy the code
(2) Examples of backtracking algorithm
After we put in the above problem solving framework, and then through the way of drawing the steps presented, the backtracking algorithm will be easily solved. Let’s look at a few examples.
2.1 all arrangementForce buckle 46. Full alignment
2.1.1 Problem Description
Given an array nums that does not contain duplicate digits, return all possible permutations. You can return the answers in any order.
Example 1:
Output: input: nums = [1, 2, 3], [[1, 2, 3], [31] 1,,1,3 [2], [2,3,1], [3,1,2], [3, 2, 1]]Copy the code
Example 2:
Input: nums = [0,1] output: [[0,1],[1,0]Copy the code
Example 3:
Input: nums = [1] Output: [[1]Copy the code
2.1.2 Problem analysis
The complete permutation problem of [1,2,3] is equal to the complete permutation of 1 + [2,3],2 + [1,3], and 3 + [1,2]
- For the first choice we can choose one, two, three
Think carefully if this corresponds to the horizontal for loop described above
- For the second selection, avoid selecting the element we selected the first time
This is simply recorded in a map
- By the time we get to the third one, we’ll know
Recursive termination conditions
- Undo what you just selected, see if there are any other options, if there are no other options, continue to undo. (This step may be a little abstract, but you’ll understand when you see the code.)
The process that we just analyzed, the process of choosing from the first to the third, each time carries with it the result of this selection, is actually a recursive process.
Image credit: The Stupid Pig Demolition Team
2.1.3 the answer
var permute = function(nums) {
const res = [] // Store all results
const map = {} // Records whether the current element exists
const dfs = (arr) = >{
if(arr.length==nums.length){
res.push(arr.slice()) // Make a shallow copy of the array so that subsequent operations on the array will not affect the res value
return
}
for(const num of nums){ // traverse horizontally
if(map[num]){ // This element is already selected
continue
}
map[num] = true
arr.push(num) // Select the element
dfs(arr) / / recursion
arr.pop() // Undo the selection
map[num] = false
}
}
dfs([])
return res
};
Copy the code
2.2 Restoring the IP AddressRestore the IP address
2.2.1 Problem Description
A valid IP address consists of four integers (each integer is between 0 and 255, and cannot contain a leading 0), separated by a hyphen (.).
For example, “0.1.2.201” and “192.168.1.1” are valid IP addresses, but “0.011.255.245”, “192.168.1.312” and “[email protected]” are invalid IP addresses. Given a numeric string s to represent an IP address, return all possible valid IP addresses that can be formed by inserting a ‘.’ into s. You cannot reorder or delete any digits in s. You can return the answers in any order.
Example 1:
Enter: s ="25525511135"Output:"255.255.11.135"."255.255.111.35"]
Copy the code
Example 2:
Enter: s ="0000"Output:"0.0.0.0"]
Copy the code
Example 3:
Enter: s ="101023"Output:"1.0.10.23"."1.0.102.3"."10.1.0.23"."10.10.2.3"."101.0.2.3"]
Copy the code
2.2.2 Problem Analysis
Horizontal for loops: How many can we select at most at a time?
- According to the question, choose at most three and at least one at a time
- The selected size ranges from 0 to 255
- If the selection is greater than or equal to two digits, it cannot appear
0X
0XX
That’s the case
Vertical recursion: Determine termination conditions
- I have selected the IP address four times, but I have not finished selecting the IP address
- The number of selected lengths exceeds the length of the entire IP address. Procedure
Select the way to record the subscript of the IP string using index value
Image credit: The Stupid Pig Demolition Team
The backtracking algorithm will exhaust all the nodes
Find all possible combinations.
Then the answer
var restoreIpAddresses = function(s) {
const res = [] / / the result set
// ipArr stores every possible IP address, when its length is 4 recursive termination condition judgment
const dfs = (ipArr,index) = >{
if(ipArr.length==4) {if(index==s.length){// The length of the string is exactly equal to IP
res.push(ipArr.join('. '))}// The recursion is terminated if the selection is not complete or the length exceeds the IP string length
return
}
// Not finished yet
for(let len=1; len<=3; len++){// Select one to three at a time
if(len! =1&&s[index]=='0') return // When selecting 2-3 elements, do not start with '0'
const ipChild = s.substr(index,len) // Intercepts the subIP address we selected just now
if(+ipChild>255) return // Range restriction
ipArr.push(ipChild) / / add
dfs(ipArr,index+len)
ipArr.pop() // backtrace (undo add)
}
}
dfs([],0)
return res
};
Copy the code
2.3 Combination SumForce buckle 39. Sum of combinations
2.3.1 Problem Description
Given an array of integer candidates with no duplicate elements and a target integer target, find all the different combinations that the candidates can make number and target, and return them as a list. You can return these combinations in any order.
The same number in the candidates can be selected repeatedly without limit. The two combinations are different if at least one number is chosen differently. For a given input, the number of different combinations of and for target is guaranteed to be less than 150.
Example 1:
Enter: candidates = [2.3.6.7], target = 7Output: [[2.2.3], [7]]
Copy the code
Explanation: 2 and 3 can form a set of candidates, 2 + 2 + 3 = 7. Note that 2 can be used more than once. 7 is another candidate. 7 is equal to 7. There are only two combinations.
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
2.3.2 Problem Analysis
Question of the soul? : Will this question choose to use backtracking algorithm?
If you look at the common features of the problems, you’ll see that you can make choices every time, and the expansion of choices is an n-fork tree. For example, I choose [1,2, 3, 4] and then find that 3 does not meet the requirements. I need to go back to the state [1,2] before I choose again. When faced with this kind of problem, the problem usually requires a certain result set, not a number/Max/min condition. Intensive practice will help you quickly come up with backtracking algorithms when faced with these kinds of problems.
The solution to this problem is easier than the previous two, and the constraints are clear
Horizontal for loop:
- You can select any element at a time (you can select it repeatedly)
Vertical recursion:
- The recursion terminates when the selected sum is greater than or equal to the target value
[2,2, 2], [2,3,2], [2,3,2].
How do you do this? It’s very simple. You just put the initial index of an element.
2.3.3 the answer
var combinationSum = function(candidates, target) {
const res = [] / / the result set
const dfs = (start,path,sum) = >{
if(sum>=target){ // Recursive termination condition
if(sum===target){
res.push(path.slice())
}
return
}
for(leti=start; i<candidates.length; i++){ path.push(candidates[i]) dfs(i,path,sum+candidates[i]) path.pop()/ / back
}
}
dfs(0[],0)
return res
}
Copy the code
2.4 Parenthesis GenerationForce buckle 22. Parenthesis generation
2.4.1 Problem Description
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
2.4.2 Problem Analysis
This problem can be solved by using the stack, which I won’t show you here. So let’s do it the same way we do dynamic programming.
Horizontal for loop:
- Every time we
You can choose either the open parenthesis or the close parenthesis
Vertical recursion:
- Recursive termination condition where the length of the current parenthesis selection is equal to twice the target value (a pair of parentheses corresponds to n=1)
Photo source:Stupid pig blast team
2.4.3 the answer
var generateParenthesis = function(n) {
const res = [] / / the result set
// l indicates the number of options left in the open parenthesis, r indicates the close parenthesis; STR represents the result of the current selection
const dfs = (l,r,str) = >{
if(str.length==n*2){
res.push(str)
return
}
if(l>0) {// The left parentheses can be selected as long as they are left
dfs(l-1,r,str+'(')}if(r>l){ Optional if the number of left parentheses is greater than the number of left parentheses
dfs(l,r-1,str+') ')
}
}
dfs(n,n,' ')
return res
};
Copy the code
2.5 String ArrangementToe offer38. String alignment
2.5.1 Problem Description
Enter a string and print out all permutations of the characters in the string.
You can return this array of strings in any order, but no repeating elements.
Example:
Enter: s ="abc"Output:"abc"."acb"."bac"."bca"."cab"."cba" ]
Copy the code
2.5.2 Problem Analysis
If you look closely at the above four problems, you’ll immediately think of backtracking when you get this one. Because it’s obviously an n-tree expansion. Horizontal for loop: Select elements
- For the first time, you can select any element, and the following elements must be current
Choose the path
It didn’t show up. For example,a->b
And then you can’t choose EITHER A or B
Vertical recursion: Determine the termination conditions for recursion
- Pass in num, which records the length of the path and terminates recursion when the length equals the length of the destination
2.5.3 answer
Version 1
var permutation = function(s) {
const res = []
const used = {}
const dfs = (num,path) = >{
if(num==s.length){
res.push(path.join(' '))
return
}
for(const c of s){
if(used[c]){
continue
}
used[c] = true
path.push(c)
dfs(num+1,path)
used[c] = false
path.pop()
}
}
dfs(0[]),return res
};
Copy the code
Version 1 will look very similar to the full permutation we did before. But if the test case is AAC and it doesn’t pass, then we don’t mark the value, but instead, we mark the subscript.
Another problem with marking subscripts is repetition: again, taking aac as an example, subscripts 0,1,2 are the same as subscripts 1,0,2. So we also need to do a de-redo operation on the result.
var permutation = function(s) {
const res = [] / / the result set
const used = [] // Record the selection path subscript
const dfs = (path) = >{
if(path.length==s.length){ // Recursive termination condition
res.push(path)
return
}
for(let i=0; i<s.length; i++){if(used[i]) continue
used[i] = true
// Path is used as a string directly, so when the recursive stack finishes, it automatically backtracks
dfs(path+s[i])
used[i] = false
}
}
dfs(' ')
return [...new Set(res)]
}
Copy the code
2.6 Sum of Combinations IIForce buckle 40. Combination sum II
2.6.1 Problem Description
Given a set of numbered candidates and a target number, find all combinations of the candidates that can be summed as 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
2.6.2 Problem Analysis
Compared with the first version of the combined total, this version has the following main problems;
- The candidate id candidates can be repeated
- Each number can only be used once in each combination
For a simple example [1,1,7], target = 8 element subscript 0->2 is the same as element subscript 1->2. So, based on version 1, let’s sort the candidates first. Apply a constraint to the horizontal for loop to see if it is the same as the previous element, and skip if it is.
2.6.3 answer
var combinationSum2 = function(candidates, target) {
const res = [] / / the result set
const used = [] // Use subscripts to record paths
candidates.sort((a,b) = >a-b)
// start-> record subscript sum-> current and path-> select path
const dfs = (start,sum,path) = >{
if(sum>=target){ // Recursive termination condition
if(sum==target){
res.push(path.slice())
}
return
}
// Horizontal for loop,start to avoid using the same element twice
for(leti=start; i<candidates.length; i++){// Cut branches with different subscripts but the same result
if(i! =0&&candidates[i]==candidates[i-1]&&used[i-1]) {continue
}
used[i] = true
path.push(candidates[i])
dfs(i+1,sum+candidates[i],path)
path.pop() / / back
used[i] = false
}
}
dfs(0.0[]),return res
};
Copy the code
2.7 combinationForce buckle 77. Combination
2.7.1 Problem Description
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
2.7.2 Problem Analysis
Horizontal for loop:
- You can pick anything from 1 minus n, but you can’t
Go back to
So if you pick 3, you can’t pick 1 again, you can’t pick 3 again, so you don’t have to repeat the result. This can be solved by starting the subscript
Vertical recursion:
- The recursive termination condition is: the length of the currently selected path is equal to k
2.7.3 the answer
In fact, I wrote it here, if the previous cases are knocked aside, this problem can actually come up with ideas in 2 minutes. Implementation is also not difficult.
var combine = function(n, k) {
const res = [] // Process the result set
// start record subscript path Record the selected path
const dfs = (start,path) = >{
if(path.length==k){ // Recursive termination condition
res.push(path.slice())
return
}
for(leti=start; i<=n; i++){ path.push(i) dfs(i+1,path)
path.pop()
}
}
dfs(1[]),return res
};
Copy the code
(iii) More backtracking:
- 257- All paths to a binary tree
- 【 medium 】17- a combination of letters for a telephone number
- 【 medium 】79- Word search
- [Medium] 332- Reschedule
Increased difficulty:
- Queen 51-N
- [medium] 377- Sum of combinations ⅳ
- 【 medium 】60- the KTH rank
Even harder
- [Difficulty] 37- Solving sudoku
4. Conclusion/conclusion
See here certainly is the classmate that loves study very much, right say is you 😜.Copy the code
We briefly summarize several characteristics of backtracking algorithm:
- Backtracking algorithm is a continuous exhaustive (recursive) process, is a kind of
Don't look back until you hit the south wall
In the algorithm. - Many backtracking problems can be reduced to one
N fork tree
The termination condition of the recursion is determined vertically by horizontal (each selection of the for loop), which can often be written out quicklyThe framework
. - If you don’t know anything, do
drawing
You mustdrawing
🌍
Well this article is introduced here, if it is helpful to you, welcome to give me a point 👍, pay attention not to get lost, I am Shao Xiaobai, personal wechat: XpTZ15387507459, there are problems can communicate with you.