# What is backtracking

Backtracking method can also be called backtracking search method. It is a way of search. Backtracking is a by-product of recursion. As long as there is recursion, there will be backtracking.

The backtracking method can generally solve the following problems:

Combination problem: find the set of k numbers in N numbers according to certain rules. Combination does not emphasize the order of elements, and arrangement emphasizes the order of elements.

Cutting problem: there are several cutting methods for a string according to certain rules

Subset problem: how many qualified subsets are there in an N-number set

Arrangement problem: N numbers are arranged according to certain rules, and there are several arrangement modes

Chessboard problem: Queen N, Sudoku, etc

How about the performance of backtracking method? Although backtracking method is difficult and difficult to understand, backtracking method is not an efficient algorithm.

Because the essence of backtracking is exhaustive, exhausting all possibilities, and then selecting the answers we want. If we want to make backtracking more efficient, we can add some pruning operations, but it can't change. Backtracking is the essence of exhaustive.

The problems solved by backtracking can be abstracted into tree structure, and all the problems solved by backtracking can be abstracted into tree structure!

Because the backtracking method solves the recursive search of subsets in the set, the size of the set constitutes the width of the tree, the depth of recursion and the depth of the tree.

Recursion must have termination conditions, so it must be a highly limited tree (N-ary tree).

# Backtracking template

1. Backtracking function template return value and parameters

1) Determine return value:

In the backtracking algorithm, my habit is to name the function backtracking. The return value of the function in the backtracking algorithm is generally void. Let's take another look at the parameters. Because the parameters required by the backtracking algorithm are not as easy to be determined at one time as the binary tree recursion, we usually write the logic first, and then fill in the parameters as needed.

2) Termination conditions

When the termination condition is reached, it can be seen in the tree. Generally, when the leaf node is found, an answer satisfying the condition is found. Store the answer and end the recursion of this layer.

3) Ergodic logic

for horizontal loop searches each element in the set, and the size of the set is the width of the tree

The depth of recursion constitutes the depth of the tree.

for loop can be understood as horizontal traversal, and backtracking (recursion) is vertical traversal

Template code:

void backtracking(parameter) { if (Termination conditions) { Storage results; return; } for (Selection: Elements in the collection of this layer (the number of node children in the tree is the size of the collection)) { Processing node; backtracking(Path, selection list); // recursion Backtracking, undo processing results } } }

# 1, Combinatorial problem

1. Combination 1 -- 77 questions

1,... n numbers, return the combination of k numbers.

n is the width of the tree and k is the depth of the tree.

Set two global variables to put the final result and the result in the process.

Recursive termination condition: k elements are added to the path, indicating that one is found.

backtracking (recursive function) traverses deep through continuous calls. It will always encounter leaf nodes and return when it encounters leaf nodes.

The following part of backtracking is the backtracking operation. Undo the result of this processing.

vector<vector<int>> result; // A collection of qualified results vector<int> path; // Used to store qualified single results void backtracking(int n, int k, int startIndex) //Recursive termination condition if (path.size() == k) { result.push_back(path); return; } for(int i = startIndex;i<=n;i++){ path.push(i); backtracking(n,k,i+1);//Recursion, control the vertical traversal of the tree, and note that the next layer search should start from i+1 path.pop_back();//to flash back } public: vector<vector<int>> combine(int n, int k) { result.clear(); // Can not write path.clear(); // Can not write backtracking(n, k, 1); return result; }

Pruning Optimization:

The place where pruning can be performed is at the starting position selected by the for loop of each layer in the recursion.

If the number of elements after the start position selected by the for loop is not enough, there is no need to search.

Assuming n=4,k=4, the traversal from i = 2 is meaningless.

Next, let's look at the optimization process as follows:

Number of selected elements: path.size();

The number of elements required is: k - path.size();

In the set n, you should start traversal at most from the starting position: n - (k - path.size()) + 1

code:

vector<vector<int>> result; vector<int> path; void backtracking(int n, int k, int startIndex) { if (path.size() == k) { result.push_back(path); return; } for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // Optimized place path.push_back(i); // Processing node backtracking(n, k, i + 1); path.pop_back(); // Backtracking, undo processing node } } public: vector<vector<int>> combine(int n, int k) { backtracking(n, k, 1); return result; }

2. Combination 2 - 216 questions

Find the combination of all k numbers whose sum is n. Only positive integers of 1 - 9 are allowed in the combination, and there are no duplicate numbers in each combination.

vector<vector<int>> result; vector<int> path; void backtracking(int n, int k, int target, int startIndex, int sum){ //termination if(sum == target && path.size() == k){ result.push_back(path); return; } for(int i = startIndex;i<=n;i++){//Can prune, I < = n - (k - path. Size()) + 1 path.push_back(i); sum += i; backtracking(n,k,target,i+1, sum); sum-=i; path.pop_back(); } } public: vector<vector<int>> combinationSum3(int k, int n) { result.clear(); // Can not add path.clear(); // Can not add backtracking(n, k, 0, 1); return result; } //termination

3. Combination 3-17 questions

Given a string containing only the numbers 2-9, returns all the letter combinations it can represent.

Combination of telephone numbers. Think about how to associate characters with numbers. The index size is the recursion depth, and the horizontal traversal boundary is the length of each string in lettermap.

class Solution { public: const string lettermap[10] = { "", "", "abc", "def", "ghi", "jkl","mno","pqrs","tuv","wxyz" }; vector<string> result; string s; void backtracking(const string & digits, int index) { if(index == digits.size()) { result.push_back(s); return; } int digit = digits[index] - '0';//This indicates the association of letters and numbers string letters = lettermap[digit]; for(int i=0; i < letters.size();i++){ s.push_back(letters[i]); backtracking(digits, index+1); s.pop_back(); } } vector<string> letterCombinations(string digits){ s.clear(); result.clear(); if(digits.size()==0) return result; backtracking(digits,0); return result; } };

4. Combination 4 -- 39 questions

Given an array of candidates without duplicate elements and a target number target, find all combinations in candidates that can make the sum of numbers target.

This problem is that in the case of repetition, the original set elements are selected without restriction. Here, the starting index of recursion is i, which is no longer i+1, indicating that it can be retrieved from the last element without starting from the next element.

class Solution { public: vector<int> path; vector<vector<int>> result; int sum; void backtracking(vector<int>& candidates, int target, int startIndex){ if(sum > target) return; if(sum == target){ result.push_back(path); return; } for(int i = startIndex; i<candidates.size();i++){ sum+= candidates[i]; path.push_back(candidates[i]); backtracking(candidates, target, i);//Take i here, not i+1 path.pop_back(); sum-=candidates[i]; } } vector<vector<int>> combinationSum(vector<int>& candidates, int target) { sum = 0; backtracking(candidates, target, 0); return result; } };

5. Combination 5-40 questions

Given an array of candidates and a target number target, find out all combinations in candidates that can make the sum of numbers target. Each number in candidates can only be used once in each combination.

The difference from the previous question is that there are duplicate elements in the original set. Therefore, the repeated combination should be removed in the process of search. We all know that combinatorial problems can be abstracted into a tree structure, so "used" has two dimensions in this tree structure, one is used on the same branch, and the other is used on the same tree layer. Looking back at the title, elements can be repeated in the same combination. It's okay how to repeat, but the two combinations can't be the same** Therefore, what we need to redo is the "used" on the same tree layer, and the elements on the same branch are all elements in the same combination, so we don't need to redo. Emphasize that if the tree layer is de duplicated, you need to sort the array** Introduce a used array,

class Solution { public: vector<int> path; vector<vector<int>> result; void backtracking(vector<int>& candidates, int target,vector<int> &used, int sum, int start){ if(sum > target) return; if(sum == target) { result.push_back(path); return; } for(int i = start;i<candidates.size();i++){ if(i>0&&candidates[i-1] == candidates[i] && used[i-1] == 0) continue;//This contains the de duplication logic of the same tree layer. path.push_back(candidates[i]); sum+=candidates[i]; used[i] = 1; backtracking(candidates, target, used, sum, i+1); path.pop_back(); sum-=candidates[i]; used[i] =0; } } vector<vector<int>> combinationSum2(vector<int>& candidates, int target) { vector<int> used(candidates.size(),0); sort(candidates.begin(), candidates.end()); backtracking(candidates, target, used, 0, 0); return result; } };

# 2, Cutting problem

1. Cutting a - 131 questions

Given a string s, s is divided into some substrings so that each substring is a palindrome string.

It can be abstracted into a tree structure, such as "aab". First cut a, then aa, and finally aab. Recursion is used for vertical traversal, for loop is used for horizontal traversal, and the cutting line is cut to the end of the string, indicating that a cutting method has been found.

First judge palindromes.

bool huiwen(string &s, int start, int end){ for(int i =start, j = end;i<j;i++,j--){ if(s[i]!=s[j]) return false; } return true; }

Full code:

class Solution { public: vector<string> path; vector<vector<string>> result; bool huiwen(string &s, int start, int end){ for(int i =start, j = end;i<j;i++,j--){ if(s[i]!=s[j]) return false; } return true; } void backtracking(string &s,int start){ if(start >= s.size()){ result.push_back(path); return; } for(int i = start;i<s.size();i++){ if(huiwen(s,start,i)){ string str = s.substr(start, i-start+1); //Note that the truncated string is added to the path, the start index of the string is start, the termination is I, and the whole string is (start, i-start+1); path.push_back(str); backtracking(s,i+1); path.pop_back(); } } } vector<vector<string>> partition(string s) { backtracking(s,0); return result; } };

2. Cutting II - question 93

Given a string containing only numbers, restore it and return all possible IP address formats.

A valid IP address consists of exactly four integers (each integer is between 0 and 255 and cannot contain a leading 0). Integers are separated by '.'.

Introduce a pointnum. Terminate when pointnum ==3.

Or you should first judge whether the intercepted string is legal.

Judgment conditions:

The number starting with 0 in the segment is illegal

If the segment is greater than 255, it is illegal

bool isValid(const string &s, int start, int end){ if(start == '0') return false; int num = 0; for(int i = start;i<=end;i++){ num = num*10 + s[i]-'0'; if(num>255) return false; } return true;

Full code:

class Solution { public: vector<string> result; bool isValid(const string &s, int start, int end){ if (start > end) { return false; //It should be noted here that 1.1.11. Will occur without. } if(s[start] == '0' && start!=end) return false; int num = 0; for(int i = start;i <= end;i++){ num = num*10 + s[i]-'0'; if(num>255) return false; } return true; } void backtracking(string &s, int start, int pointnum){ if(pointnum == 3){ if(isValid(s, start, s.size()-1)){ result.push_back(s); return; } } for(int i = start;i<s.size();i++){ if(isValid(s,start,i)){ s.insert(s.begin()+i+1,'.'); pointnum++; backtracking(s, i+2,pointnum); pointnum--; s.erase(s.begin()+i+1); } } } vector<string> restoreIpAddresses(string s) { if(s.size()>12) return result; backtracking(s, 0, 0); return result; } };

# 3, Subset problem

If the subset problem, combination problem and segmentation problem are abstracted into a tree, then the combination problem and segmentation problem are to collect the leaf nodes of the tree, and the subset problem is to find all nodes of the tree and record all nodes when traversing the tree, which is the required subset set.

In fact, subset is also a combinatorial problem, because its set is disordered, and subset {1,2} is the same as subset {2,1}.

Since it is out of order, the fetched elements will not be fetched repeatedly. When writing the backtracking algorithm, for should start from startIndex instead of 0!

1. Subset 1 - 78 questions

Given a set of integer arrays nums without duplicate elements, all possible subsets (power sets) of the array are returned.

class Solution { public: vector<int> path; vector<vector<int>> result; void backtracking(vector<int>& nums,int start){ result.push_back(path);//The subset problem must be placed at the beginning to collect all nodes if(start>=nums.size()){ return; } for(int i = start; i<nums.size();i++){ path.push_back(nums[i]); backtracking(nums, i+1);//It cannot be repeated. Take it from the next one path.pop_back(); } } vector<vector<int>> subsets(vector<int>& nums) { backtracking(nums,0); return result; } };

2. Subset 2 - 90 questions

Given a set of integer arrays nums without duplicate elements, all possible subsets (power sets) of the array are returned. The input has duplicates, but the solution set cannot contain duplicate subsets. Therefore, it is necessary to remove the weight on the same layer. Use the used array. (don't forget to sort)

class Solution { public: vector<int> path; vector<vector<int>> result; void backtracking(vector<int>& nums, int start, vector<int> &used){ result.push_back(path); if(start >= nums.size()) return; for(int i = start;i<nums.size();i++){ if(i>0 && nums[i-1] == nums[i] && used[i-1] == 0) continue; path.push_back(nums[i]); used[i] = 1; backtracking(nums, i+1, used); used[i] = 0; path.pop_back(); } } vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<int> used(nums.size(),0); sort(nums.begin(), nums.end()); //Don't forget to sort!!! backtracking(nums,0,used); return result; } };

# 4, Permutation problem

First, the permutation is ordered, that is, [1,2] and [2,1] are two sets, which is different from the subsets and combinations previously analyzed.

It can be seen that element 1 has been used in [1,2], but 1 will be used once in [2,1], so you don't need to use startIndex to deal with the arrangement problem, starting from 0. However, the permutation problem requires a used array to mark the selected elements. The used array actually records which elements are used in the path at this time. An element in an permutation can only be used once. When the size of the path of the array collecting elements reaches the same size as the num array, it indicates that a full arrangement has been found, which also indicates that the leaf node has been reached.

Note two things: 1) void backtracking (vector & num, vector used), because I don't care about startindex. So no more. 2) if(used[i] == 1) continue;// The elements already included in path are skipped directly.

1. Arrange one - 90 questions

Given a sequence without repeated numbers, all possible permutations are returned.

class Solution { public: vector<vector<int>> result; vector<int> path; void backtracking(vector<int>& nums, vector<int> used){ if(nums.size() == path.size()){ result.push_back(path); return; } for(int i = 0;i<nums.size();i++){ if(used[i] == 1) continue;// The elements already included in path are skipped directly path.push_back(nums[i]); used[i] = 1; backtracking(nums,used); used[i] = 0; path.pop_back(); } } vector<vector<int>> permute(vector<int>& nums){ vector<int> used(nums.size(),0); backtracking(nums, used); return result; } };

2. Arrangement 2 - 47 questions

Given a sequence nums that can contain repeated numbers, return all non repeated full permutations in any order. This problem needs to be de duplicated.

class Solution { public: vector<int> path; vector<vector<int>> result; void backtracking(vector<int>& nums, vector<int> &used){ if(path.size() == nums.size()){ result.push_back(path); return; } for(int i = 0;i<nums.size();i++){ if(i>0 && nums[i-1] == nums[i] && used[i-1] == 0){//duplicate removal continue; } if(used[i] == 0){ used[i] = 1; path.push_back(nums[i]); backtracking(nums, used); path.pop_back(); used[i] = 0; } } } vector<vector<int>> permuteUnique(vector<int>& nums) { vector<int> used(nums.size(),0); sort(nums.begin(), nums.end()); backtracking(nums, used); return result; } };

# 5, Other issues

1. Queen N - question 51

The n queen problem studies how to place n queens in n × N's chessboard, and the Queens can't attack each other.

Give you an integer n and return the solution of all different N Queen problems.

Each solution contains a different chess placement scheme for the n-queen problem, in which 'Q' and '.' represent the queen and the vacancy respectively.

Because only one queen is placed in each row and column, only one layer of for loop is needed to traverse one row and return to traverse the columns, and then determine the unique position of the queen one row and one column.

First judge whether it is valid, including checking rows and columns, checking 45 ° and 135 °. Check the diagonal here. Be sure to check from back to front, from bottom right to top left, from bottom left to top right.

Create a two-dimensional string array: vector str;

class Solution { vector<vector<string>> result; bool isValid(int n, int row, int col,vector<string>&chessboard){ //that 's ok for(int i =0;i<row;i++){ if(chessboard[i][col] == 'Q') return false; } for(int i = 0;i<col;i++){ if(chessboard[row][i] == 'Q') return false; } for(int i = row-1,j=col-1; i>=0&&j>=0;i--,j--){ if(chessboard[i][j] == 'Q') return false; } for(int i = row-1, j = col+1; i>=0&&j<n; j++,i--){ if(chessboard[i][j] == 'Q') return false; } return true; } void backtracking(int n, int row, vector<string>&chessboard){ if(row == n) { result.push_back(chessboard); return; } for(int col = 0;col<n;col++){ if(isValid(n, row, col, chessboard)){ chessboard[row][col] = 'Q'; backtracking(n, row+1, chessboard); chessboard[row][col] = '.'; } } } public: vector<vector<string>> solveNQueens(int n) { vector<string> chessboard(n,string(n, '.')); backtracking(n,0,chessboard); return result; } };

2. Path planning - 51 questions

Given a string two-dimensional array [from, to] of air tickets, two members in the sub array represent the airport locations where the aircraft departs and lands respectively, and the itinerary is re planned and sorted. All these tickets belong to a gentleman departing from JFK (Kennedy International Airport), so the trip must start from JFK and return to the smallest trip combination in natural order of characters.

Key: one airport maps to multiple airports, and the airports are arranged in alphabetical order. One airport maps to multiple airports. You can use std::unordered_map. If you want multiple airports to be in order, you can use std::map or std::multimap or std::multiset.

1)unordered_ map<string, map<string, int>> targets: unordered_ Map < departure airport, map < arrival airport, number of flights > > targets

Traversing unordered_ During the process of map < departure airport, map < arrival airport, number of flights > > targets, you can increase or decrease the number of flights in the field to mark whether the arrival airport has been used. If the "number of flights" is greater than zero, the destination can still fly. If the "number of flights" is equal to zero, the destination cannot fly. There is no need to delete or add elements to the set.

2) Both targets and result need to be initialized: first count the mapping relationship:

for(const vector<string> & vec:tickets){ targets[vec[0]vec[1]]++;} //vec[0] is the departure airport, vec[1] is the arrival airport and counts the number of flights arriving at the airport (for example: {"JFK","KUL"}, vec[0] is JFK, vec[1] is KUL); result.push_back("JFK"); // Starting Airport

3) Note that the return value of the function is bool, because we only need to find a journey, which is the only route to the leaf node in the tree structure.

Termination condition judgment: set a variable of the number of flights, and terminate when the result number is equal to the number of flights + 1.

if (result.size() == ticketNum + 1) { return true; }

4) Start the for loop and traverse the map < string, int > pairs in targets.

for(pair<const string, int>& target : targets[result[result.size()-1]]){ if(target.second >0){ result.push_back(target.first); target.second--; if(backtracking(ticketnum)) return true; result.pop_back(); target.second++; } } return false; }

Complete code

class Solution { public: unordered_map<string, map<string, int>> targets; vector<string> result; bool backtracking(int ticketnum ){ if(result.size() == ticketnum+1){ return true; } class Solution { public: unordered_map<string, map<string, int>> targets; vector<string> result; bool backtracking(int ticketnum ){ if(result.size() == ticketnum+1){ return true; } for(pair<const string, int>& target : targets[result[result.size()-1]]){ if(target.second >0){ result.push_back(target.first); target.second--; if(backtracking(ticketnum)) return true; result.pop_back(); target.second++; } } return false; } vector<string> findItinerary(vector<vector<string>>& tickets) { targets.clear(); // vector<string> result; for(const vector<string> & vec : tickets){ targets[vec[0]][vec[1]]++; } result.push_back("JFK"); backtracking(tickets.size()); return result; } }; vector<string> findItinerary(vector<vector<string>>& tickets) { targets.clear(); // vector<string> result; for(const vector<string> & vec : tickets){ targets[vec[0]][vec[1]]++; } result.push_back("JFK"); backtracking(tickets.size()); return result; } };

3. Solving Sudoku - 37 questions

Write a program to solve the Sudoku problem by filling in spaces.

The solution of a Sudoku should follow the following rules: the numbers 1-9 can only appear once in each line. The numbers 1-9 can only appear once in each column. The numbers 1-9 can only appear once in each 3x3 uterus separated by a thick solid line. The blank space is indicated by '.'.

1) The return value of the recursive function needs to be of bool type. Because the solution returns immediately when it finds a matching condition (on the leaf node of the tree), which is equivalent to finding a unique path from the root node to the leaf node, it needs to use the bool return value.

2) It can be seen from the tree that what we need is a two-dimensional recursion (that is, two for loops are nested with recursion). A for loop traverses the rows of the chessboard and a for loop traverses the columns of the chessboard. After determining the rows and columns, it is possible to recursively traverse this position and put 9 numbers!

3) Valid judgment:

There are three dimensions to judge whether the chessboard is effective:

Whether peers repeat

Is the same column repeated

9. Does Gong gri repeat

class Solution { public: bool isValid(int row, int col, char val, vector<vector<char>> &board){ for(int i=0;i<9;i++){ if(board[row][i] == val) return false; } for(int i=0;i<9;i++){ if(board[i][col] == val) return false; } int startrow = (row/3) *3;//Here we should pay attention to dividing by 3 and rounding × three int startcol= (col/3) *3; for(int i = startrow; i<startrow+3;i++){ for(int j = startcol;j<startcol+3;j++){ if(board[i][j] == val) return false; } } return true; } bool backtracking(vector<vector<char>> &board){ for(int i = 0;i < board.size(); i++){ for(int j = 0;j < board[0].size();j++){ if (board[i][j] != '.') continue;//Termination condition, all cells are filled. for(char k = '1';k<='9';k++){ if(isValid(i,j,k,board)){ board[i][j] = k; if(backtracking(board)) return true; board[i][j] = '.'; } } return false; } } return true; } void solveSudoku(vector<vector<char>>& board) { backtracking(board); } };

# summary

Combinatorial problem: when the order is not emphasized, the generated results are not repeated, and the original set elements cannot be retrieved repeatedly, each startIndex is retrieved from i+1; If u and the original set elements can be repeated, start with i each time. If there are duplicates in the original set elements, the same layer needs to be de duplicated. The used array is introduced, sorted first, and then judged

if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) { continue; }

Cutting problem: intercept the string s.substr(startIndex, i - startIndex + 1), and then backtracking(s, i + 1);

Subset problem: collect all tree nodes. result.push_back(path); / / collect subsets and put them on the top of terminating the addition, otherwise you will miss yourself. Set startIndex and backtracking from i+1. Backtracking (Num, i+1); if there are duplicate elements in the collection, and the obtained subsets need to be de duplicated.

if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) { continue; }

Permutation problem: firstly, permutation is orderly, that is to say, [1,2] and [2,1] are two sets, which are different from the subsets and combinations analyzed before. You can repeatedly take elements without setting startIndex, and i starts from 0 every time. Vertically on the same branch, if (used [i] = = true) Continue; / / skip directly the elements already included in the path. The used array actually records which elements are used in the path at this time. An element in an arrangement can only be used once. If there are duplicates in the original set, sorting and de duplication will be added.

if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) { continue; }