Foreword
Backtracking is a mindless rush. After hitting a wall, you retreat and continue to do it. It belongs to a kind of violent problem-solving idea;
In fact, the same is true. When we encounter some classified problems and can't think of a more sophisticated solution, we first consider violence, enumerate all situations, and then deal with them. Backtracking is such a violent method
The next tab is to learn about the conventional sorting algorithm
Drain it
After brushing these double pointer questions, you can tear the front end of the interview by hand
After brushing the 12 sliding windows, you can tear the front end by hand
After brushing these 15 dp questions, I can only take a chance to enter the big factory
text
In the process of doing backtracking questions, I will find that I am very confused, because many questions do not seem to need to return. In the process of performing the next step, I will make a good judgment, and then curb the possible failure. At this time, generally, what can continue to go down belongs to the OK operation. In fact, we can call this method "pruning"
I once fell into a deep thought. Is backtracking useless? As long as the brain is OK, in fact, it's good to prune and go back. Until I think of the core idea of backtracking, it's actually a violent solution. That is, if you can use other methods, you don't have to go back. It's a better idea. Generally, backtracking will be more complex
So when do you use backtracking? You can't preset the ending, or your choice is not only related to the choices of adjacent layers, but will have an impact on deeper layers, for example 51. Queen n
What we need is a complete chessboard. The choice of each layer will affect the layout of the whole chessboard. At this time, it is too difficult to think out all the possible situations at the moment of playing chess. At this time, backtracking is a good choice
For some that only have an impact on the upper level, pruning} is a good choice at this time;
In fact, when making a series summary, we will use a series of methods to solve as much as possible, but we also pursue multiple solutions to one problem, and what we want to achieve in the end is certainly not limited to a certain writing method, but can a come out as long as we see it;
So try to review most of the conventional tab s, and then fill them slowly, and summarize your own problem-solving scheme, which is the purpose of summarizing;
Work hard with everyone
Topic summary
array
combination
subset
cutting
route
Queen N
Problem solution
46. Full arrangement
analysis
- It does not contain duplicate numbers and requires full arrangement, so the arrangement in different orders must be counted. In this way, you should know what values you have obtained in the enumeration process
- During enumeration, cache two arrays arr, getIndex. Arr is the array during enumeration, and getIndex is the passed value state. If the value of the corresponding subscript of the current arr is 1, it is 0 if it does not pass
- When adding a value to the temporary array arr at each layer, you need to ensure that it will not be added repeatedly. You can traverse the ARR every time you encounter it. Because the value is unique, it is also possible;
- Here, space is used for time, and getIndex array is used to cache the corresponding state. The complexity of each search is {O(1)}O(1)
- The complete array needs to be enumerated n times each time, so the time complexity is {O(n^2)}O(n2) and the space complexity is {O(n)}O(n)
var permute = function (nums) { let ret = []; const dfs = (arr, getIndex) => { if (arr.length === nums.length) { ret.push(arr); return; } for (let i = 0; i < nums.length; i++) { const num = nums[i]; if (!!getIndex[i]) continue; // If it exists, it means that it already has this value getIndex[i] = 1; dfs([...arr, num], getIndex); getIndex[i] = 0; } }; const getIndexArr = new Array(nums.length) dfs([], getIndexArr); return ret; }; Copy code
47. Full arrangement II
analysis
- Since duplicate numbers are included at this time, and there can be no duplicate values, you can consider sorting first
- Sorting ideas and question 1 have always been to cache two arrays, and because the values are repeated, we can't judge whether the values are the same, but only by subscript
- The difference is that for each backtracking, you need to judge whether the next value is the same as the current backtracking value. If it is the same, you need to skip to prevent repeated arrangement
- Time complexity {O(n^2)}O(n2), space complexity {O(n)}O(n)
var permuteUnique = function(nums) { const ret = [] const len = nums.length nums.sort((a,b)=>a-b) // sort const dfs = (arr,indexArr) => { if(arr.length === len ){ ret.push(arr) return } for(let i = 0;i<len;i++){ if(!!indexArr[i]) continue const num = nums[i] indexArr[i] = 1 dfs([...arr,num],indexArr) indexArr[i] = 0 // If the next value is the same, then you have to repeat the old way, so you'd better skip it directly while(nums[i+1]=== nums[i]) { i++ } } } dfs([],[]) return ret } console.log(permuteUnique([1,1,2])) Copy code
39. Combination sum
analysis
- candidates is a non repeating, positive integer array
- Values can be repeated, but they cannot be retrieved backwards because they are independent of the arrangement, so an initial subscript value needs to be maintained; In contrast to [combined sum IV]
var combinationSum = function(candidates, target) { const ret = [] const dfs = (start,arr,sum) => { if(sum === target){ ret.push(arr) return } if(sum>target) return for(let i = start;i<candidates.length;i++){ // Because repeated fetching is allowed, it starts from the start node every time dfs(i,[...arr,candidates[i]],sum+candidates[i]) } } dfs(0,[],0) return ret } Copy code
40. Combined sum II
analysis
- candidates is an array of positive integers with or without duplicates
- Each value in the array can only be taken once; Duplicate values cannot be taken, but duplicate values can be taken, that is, [1,1,2,3] - > can take [1,1,2], [1,3] - > 4
- In order not to get duplicate values, you have to skip the same values. At this time, you need to sort the array
- When enumerating at each layer, when duplicate values appear in the loop, cut off this part of the enumeration, because there must be the same part
- The subscript of the first input parameter of dfs should be + 1 because it cannot be retrieved repeatedly, which indicates that the last value cannot be retrieved repeatedly
var combinationSum2 = function (candidates, target) { candidates.sort((a,b)=>a-b) const ret= [] const dfs = (start,arr,sum) => { if(sum === target) { ret.push(arr) return } if(sum>target || start>= candidates.length) return for(let i = start;i<candidates.length;i++){ // Cut off the duplicate if(i > start && candidates[i] === candidates[i-1]) continue // Here, start is the index of the start enumeration, but the value inserted into the temporary array is the value of the current index dfs(i+1,[...arr,candidates[i]],sum+candidates[i]) } } dfs(0,[],0) return ret } Copy code
216. Combined sum III
analysis
- Given is not a specific array, but the length limit k, and the target value target -- equivalent to candidates is a non repeating, 1-9 positive integer array
- So it can be seen as 39. Combination sum In special circumstances, only the judgment conditions are different
var combinationSum3 = function (k, n) { const ret = []; const dfs = (start, arr, sum) => { if (arr.length === k && sum === n) { ret.push(arr); return; } if (arr.length > k || sum > n) { return; } for (let i = start + 1; i < 10; i++) { dfs(i, [...arr, i], sum + i); } }; dfs(0, [], 0); return ret }; Copy code
377. Combined total IV
Analysis - backtracking
- candidates is a non repeating, positive integer array, which can take repeated values and take different combinations
- This question is similar to Combined sum Very similar. The difference is that this question asks for the number of permutations, while question 1 asks for non repeated combinations
- Therefore, there is no need to limit the subscript of the combination start enumeration. Start from 0 every time
- Then it timed out
*/
var combinationSum4 = function (nums, target) { let ret = 0; const dfs = (sum) => { if (sum === target) { ret++; return; } if (sum > target) return; for (let i = 0; i < nums.length; i++) { dfs(sum + nums[i]); } }; dfs(0); return ret; }; Copy code
Analysis - dp
- dp[i] indicates the number of combinations that exist when the value is I
- State transition equation dp[i] = sum(dp[i-nums[k]])
- base case dp[0] = 1
var combinationSum4 = function (nums, target) { const dp = new Array(target+1) dp[0]= 1 // If the value you just get is 0, there will be 1, because not taking is also a way to take it for(let i = 1;i<target+1;i++){ dp[i] = 0 for(let j =0;j<nums.length;j++){ if(i>=nums[j]){ dp[i]+=dp[i-nums[j]] } } } return dp[target] } Copy code
78. Subset
Analysis -- find the law
- The array elements are different, and the return value does not contain duplicate subsets, that is, the position arrangement is not considered
- Since it has nothing to do with permutation, you only need to traverse num once. The values obtained without traversing once can be combined with the existing ret to form a new batch of arrays, and then combined with the old item to form a new enumeration array
- Time complexity {O(n^2)}O(n2)
var subsets = function (nums) { let ret = [[]] for(let num of nums ){ ret = [...ret,...ret.map(item => item.concat(num))] } return ret } Copy code
Analysis -- iterative backtracking
- Use the iterative method to enumerate all cases, which is no different from the traversal of multi fork tree
- Time complexity {O(N^2)}O(N2)
var subsets = function (nums) { const ret = [] const dfs = (start,arr) => { ret.push(arr) if(arr.length === nums.length || start=== arr.length) return for(let i = start;i<nums.length;i++){ dfs(i+1,[...arr,nums[i]]) } } dfs(0,[]) return ret } Copy code
90. Subset II
Analysis - duplicate values
- and 78. Subset In contrast, there are many duplicate values, and duplicate values are not allowed to appear in the return array, so it is obvious to sort first
- Then, in the backtracking process, if the value of the next iteration is the same as the current value, it will be skipped to achieve the effect of de duplication
var subsetsWithDup = function (nums) { nums.sort((a,b)=> a-b) const ret = [] const dfs = (start,arr) => { ret.push(arr) if(start === nums.length ) return // If start exceeds the subscript, it is time to get the maximum subscript value for(let i = start;i<nums.length;i++){ dfs(i+1,[...arr,nums[i]]) while(nums[i] === nums[i+1]){ i++ // duplicate removal } } } dfs(0,[]) return ret } Copy code
131. Split palindrome string
analysis
- This is a variant combination problem, because the arrangement order has been determined, as long as it is cut
- Therefore, in the traversal process, only when the substring meets the palindrome requirements can it be cut, and then go down, otherwise it is better to cut it off
- The determination of palindrome substring can be simply realized with left and right double pointers
var partition = function(s) { const ret = [] // Determine whether it is a palindrome substring function isValid(s) { if(s.length === 1) return true // Only one character let l = 0,r = s.length-1 while(l<r){ if(s[l] !== s[r]) return false l++ r-- } return true } const dfs = (start,arr) => { if(start === s.length){ ret.push(arr) return } let temp ='' for(let i =start;i<s.length;i++){ temp+=s[i] if(isValid(temp)){ dfs(i+1,[...arr,temp]) } } } dfs(0,[]) return ret }; Copy code
93. Restore IP address
analysis
- This question is similar to 131. Split palindrome string Similar
- Here is also the segmentation string, but the judgment condition is that each segment must meet the valid IP address, but the shelf is the same
- There are also many judgment conditions here. As long as the qualified conditions are counted, many branches can be cut off
var restoreIpAddresses = function (s) { const ret = []; function isValid(s) { if (s.length > 1 && s[0] == 0) return false; // Cannot start with 0 if (s >= 1 << 8) return false; // To be between [0255] return true; } const dfs = (start, arr) => { if (arr.length === 4 && start !== s.length) return; // It has been divided into four points, but it's not finished yet if (start === s.length) { if (arr.length === 4) { ret.push(arr.join(".")); } // Whether divided into four or not, they left return; } let str = ""; for (let i = start; i < s.length && i < start + 3; i++) { str += s[i]; if (isValid(str)) { dfs(i + 1, [...arr, str]); } } }; dfs(0, []); return ret; }; Copy code
112. Path sum
analysis
- The path is on the root leaf complete route and the sum is target
- dfs sequence traversal can go on
- Time complexity {O(n)}O(n)
var hasPathSum = function(root, targetSum) { let ret = false const dfs = (root,sum) => { if(ret || !root) return // As long as one road goes through, there is no need to go anywhere else sum += root.val if(!root.left && !root.right && sum === targetSum) { ret = true return } if(root.left) dfs(root.left,sum) if(root.right) dfs(root.right,sum) } dfs(root,0) return ret }; Copy code
113. Path sum II
analysis
- The root - leaf path is still found, but this time save all the qualified paths found
- Time complexity {O(n)}O(n)
var pathSum = function(root, targetSum) { const ret = [] const dfs = (root,arr,sum) => { if(!root) return sum+=root.val arr = [...arr,root.val] if(!root.left && !root.right && sum == targetSum){ ret.push(arr) } if(root.left) dfs(root.left,[...arr],sum) if(root.right) dfs(root.right,[...arr],sum) } dfs(root,[],0) return ret }; Copy code
437. Path sum III
analysis
- The path found this time can be any start end node in the tree,;
- However, the path must be downward, that is, it cannot look like a.left - a - a.right, which is actually a limiting condition to reduce the difficulty
- So it's just the same top-down traversal, but if you encounter a path that meets the requirements, you still have to continue to traverse to the leaf node
- And 112. Path sum And 113. Path sum II The biggest difference is that the path this time does not limit the starting point and ending point;
- Without limiting the end point, I can record once in the traversal process as long as the targetSum is met, up to the leaf node position, and there is no need to judge again at the leaf node
- Without limiting that the starting point is the root node, you can take any node as the starting point, that is, when you need to traverse the whole tree as the starting point, go down to find the path;
- Time complexity {O(nlogn)}O(nlogn)
var pathSum = function (root, targetSum) { let ret = 0; // This is a dfs for finding paths and with any root node const dfs = (root, sum) => { if (!root) return; sum += root.val; if (sum === targetSum) ret++; if (!root.left && !root.right) return; // The leaf node is over if (root.left) dfs(root.left, sum); if (root.right) dfs(root.right, sum); }; // This is traversing the whole tree and then continuing down const outer = (root) => { if (!root) return; dfs(root, 0); outer(root.left); outer(root.right); }; outer(root); return ret; }; Copy code
51. Queen n
Reference: leetcode-cn.com/problems/n-...
Analysis -- directly find the chessboard that meets the requirements
- The row is the depth of tree recursion, and the column is the width of each layer. The backtracking method is used for dfs traversal of the tree
- The whole process requires three parts. Traverse the tree in a backtracking way, find the qualified node chessboard[row][col], and convert the qualified two-dimensional array into a qualified string array
- Time complexity {O(n*logn)}O(n * logn)
var solveNQueens = function (n) { const ret = []; // 1. The actual walking process of Queen n -- backtracking tree const dfs = (row, chessboard) => { if (row === n) { // It has reached the leaf node-- // However, chessboard is a two-dimensional array, which cannot be push ed in casually. It needs to be copied deeply ret.push(getStrChessboad(chessboard)); return; } // Each line starts from 0 - n-1, and then goes back if it doesn't meet the requirements for (let col = 0; col < n; col++) { if (isValid(row, col, chessboard)) { // If the chessboard[row][col] meets the requirements, it is a road chessboard[row][col] = "Q"; dfs(row + 1, chessboard); chessboard[row][col] = "."; // Go back } } }; // Judge whether the current node meets the requirements of Queen N -- note that [0,n-1] here is calculated from left to right function isValid(row, col, chessboard) { // Same column for (let i = 0; i < row; i++) { if (chessboard[i][col] === "Q") { return false; } } // 45 'tilt from left to right for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { if (chessboard[i][j] === "Q") { return false; } } // 135 ` tilt from right to left for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { if (chessboard[i][j] === "Q") { return false; } } // If it is not the same column or left and right slashes, the requirements are met return true; } // Convert the N Queen of a two-dimensional array into a one-dimensional array string function getStrChessboad(chessboard) { const ret = []; chessboard.forEach((row) => { let str = ""; row.forEach((item) => { str += item; }); ret.push(str); }); return ret; } const chessboard = new Array(n).fill([]).map(() => new Array(n).fill(".")); dfs(0, chessboard); return ret; }; Copy code
52. N Queen II
analysis
- Problems and 51. Queen n It's basically the same, but the value is changed from the complete N-queen scheme to just knowing a few
- Therefore, the third part of the conversion can be deleted directly and then copied directly
var totalNQueens = function (n) { let ret = 0; const dfs = (row, chessboard) => { if (row === n) { ret++; return; } for (let col = 0; col < n; col++) { if (isValid(row, col, chessboard)) { chessboard[row][col] = "Q"; dfs(row + 1, chessboard); chessboard[row][col] = "."; } } function isValid(row, col, chessboard) { for (let i = 0; i < row; i++) { if (chessboard[i][col] === "Q") return false; } for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { if (chessboard[i][j] === "Q") return false; } for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { if (chessboard[i][j] === "Q") return false; } return true; } }; const chessboard = new Array(n).fill([]).map(() => new Array(n).fill(".")); dfs(0, chessboard); return ret; }; Copy code
@Analysis
- The backtracking process is very simple, but is there a better way to handle the judgment condition isValid
- In the first question, we want to create an instance of Queen N, so we need to use an array. Now we don't need a specific queen N, so we can show queen N in other forms without the form of array
- Three binary bits col, DLR and DRL are used to represent the values on the column, starting from the left 45 ° and starting from the right 135 ° respectively
- col here is easy to understand, because the value of i in each row will not change when the row to be judged
- For dlr, the bit corresponding to binary is tilted, and only such a value conforms to 45 ` tilt; Similarly, drl is the same Q Q . . . . . . Q . . . . . . Q . . . . . . Q . . . . .
- therefore
var totalNQueens = function (n) { let ret = 0; const dfs = (r, col, dlr, drl) => { if (r === n) { ret++; return; } for (let i = 0; i < n; i++) { // The current coordinate is converted to the value corresponding to the binary bit const _col = 1 << i; const _dlr = 1 << (r + i); // This indicates that the value of i in other lines should be 1 < < (r + i) at the current r, so we set such a value to try other values to see if they meet the requirements const _drl = 1 << (n - i + r); if ((col & _col) || (dlr & _dlr) || (drl & _drl)) continue; // As long as one is true, dfs(r + 1, col | _col, dlr | _dlr, drl | _drl); } }; dfs(0, 0, 0, 0); return ret; };
If there are friends who need to get free information, You can click here to get the information!