Notes for brushing questions with Likou - backtracking

This article is the study notes recorded by the code random , you can search the official account to learn by yourself

Table of contents

combination problem

Phone Number Alphabet

 split string

131. Partitioning palindrome strings - LeetCode https://leetcode.cn/problems/palindrome-partitioning/

IP address segmentation

Subset problem

Subset

78. Subsets - LeetCode https://leetcode.cn/problems/subsets/

 increasing subsequence

reschedule

n queen problem


Backtracking is a brute force search algorithm that solves the following problems:

Why is it a violent search, because the idea of ​​this method is to enumerate all possible results and extract the results that meet the requirements of the topic.

Therefore, backtracking is not efficient, and it is not efficient but it does not mean that it is useless. We can only use backtracking to solve some problems.

The following questions are examples:

  1. Combination problem: Find a set of k numbers in N numbers according to certain rules
  2. Segmentation question: How many ways can a string be cut according to certain rules?
  3. Subset: How many qualified subsets are there in a set of N numbers
  4. Arrangement: N numbers are arranged according to certain rules, there are several arrangement methods
  5. Chessboard Problem: N Queens, Solving Sudoku
  6. other

Backtracking is a by-product of recursion. As long as there is recursion, there will be backtracking. The essence of backtracking is exhaustive.

The size of the collection determines the width of the backtracking tree, and the depth of the recursion determines the depth of the backtracking tree

The question formula is as follows

void backtracking(参数)
{
    if(终止条件){
        存放结果;
        return;
    }

    for(选择:本层集合中的元素){
        处理节点;        
        backtracking(参数)
        回溯撤销结果
    }
}

combination problem

77. Combinations - LeetCode https://leetcode.cn/problems/combinations/

class Solution {
public:
    vector<vector<int>> res;//用于存放结果
    vector<int> path;//用来存放符合条件结果
    vector<vector<int>> combine(int n, int k) {
          
        backtracking(n,k,1);
        return res;
    }

    void backtracking(int n,int k,int index){
        if(path.size()==k){
             res.push_back(path);
             return;
         }
        for(int i=index;i<=n;i++){
            path.push_back(i);//处理节点
            backtracking(n,k,i+1);//递归
            path.pop_back();//回溯
        }
    }
    
};

Pruning optimization:

path.size() is the selected data

k-path.size() is the selected data that is still needed

Then the index starts at most n-(k-path.size())+1

+1 for the left closure since our indexing starts at one

Modify the result:

for(int i=index;i<n-(k-path.size())+1;i++){

}

Recursive tree:

 The traversed path minus the red position

216. Combination Sum III - LeetCode https://leetcode.cn/problems/combination-sum-iii/

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k,n,1);
        return res;
    }


    void backtracking(int k,int sum,int index){
        if(sum==0&&path.size()==k){
            res.push_back(path);
            return;
        } 
        if(sum<0||path.size()>k){
            return;
        }

        for(int i=index;i<=9;i++){
            //对于节点的处理
            path.push_back(i);
            sum-=i;

            backtracking(k,sum,i+1);

            sum+=i;
            path.pop_back();
        }
    }
};

39. Combination sum - LeetCode (LeetCode) https://leetcode.cn/problems/combination-sum/ The main difference between this problem and the previous problem is that elements can be selected repeatedly, and there is no limit to the number.

Consider the parameters of the backtracking function:

  • array
  • target value
  • start index startindex
  • The sum of the numbers in the path (can be obtained directly through target)

Termination condition:

if(sum==target){ res.push_back(path); return;}
if(sum>target) return;

 ac's code:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates,target,0,0);
        return res;
    }


    void backtracking(vector<int>& candidates,int target,int sum,int startindex){
        if(sum==target){ res.push_back(path); return;}
        if(sum>target) return;


        for(int i=startindex;i<candidates.size();i++){
            //对于本节点的处理
            sum+=candidates[i];
            path.push_back(candidates[i]);
            //递归
            backtracking(candidates,target,sum,i);

            //回溯
            sum-=candidates[i];
            path.pop_back();
        }
    }
};

Pruning optimization:

If the value of sum + candidates[i] means that the sum of the next round is greater than the target, there is no need to enter the next round of recursion, and add conditions in the for loop
sum + candidates[i] <= target

40. Combination Sum II - LeetCode https://leetcode.cn/problems/combination-sum-ii/ Question 40 is similar to the previous question, but there are subtle differences

  • Elements in a given collection can be repeated
  • Combinations cannot be repeated in the final result

For these two problems, we need to deduplicate the results, which are the same elements that have been used in this layer of recursion.

 Then we use an array to identify whether the elements in the array have been used , use an identification array of the same size as the given array, and create a used array to indicate whether an element has been accessed.

Parameter thinking:

  • The parameters given by the title function
  • sum represents the sum of the current elements
  • startindex represents the current position where recursion needs to start
  • used is an array that identifies whether the element has been used

The end condition is the same as above.

The code of ac is as follows

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size());
        sort(candidates.begin(), candidates.end());
        backtracking(candidates,target,0,0,used);
        
        return res;
    }
    

    void backtracking(vector<int>& candidates,int target,int sum,int startindex,    vector<bool>& used){
        if(sum==target){ res.push_back(path); return;}
        if(sum>target) return;

        
        for(int i=startindex;i<candidates.size() && sum+candidates[i]<=target;i++){
            //相对于同层之前已经使用过的不能再次使用
           
            //对于本节点的处理
            sum+=candidates[i];
            path.push_back(candidates[i]);
            used[i]=true;
            //递归
            backtracking(candidates,target,sum,i+1,used);

            //回溯
            used[i]=false;
            sum-=candidates[i];
            path.pop_back();
        }
    }
};

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

It means that the element at position i is the same as the element at position i-1, but the element at position i (false) has already been used. If there is that path, we will prune it to prevent the result from being repeated.

used[i - 1] == true , indicating that the same tree branch candidates[i - 1] have been used
used[i - 1] == false , indicating that candidates[i - 1] at the same tree level have been used

Phone Number Alphabet

17. Letter combinations of phone numbers - LeetCode https://leetcode.cn/problems/letter-combinations-of-a-phone-number/ Think about what parameters need to be transmitted:

  • path.size() determines the number of keys representing letters
  • button position index
  • The total number of keys n

 For reference:

It is found that the parameter n can not be passed as a parameter, but it has no effect.

class Solution {
public:
    vector<string> res;
    vector<char> path;
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0) return res;
        backtracking(digits.size(),0,digits);
        return res;
    }


    void backtracking(int n,int index,string digits){
        //终止条件
        if(n==path.size()){
            string s="";
            for(auto i:path){
                s+=i;
            }
            res.push_back(s);
            return ;
        }

        int sum=0;
        int flag=digits[index]-'0';
        if(flag==9 || flag==7) sum=4;
        else sum=3;

        for(int i=0;i<sum;i++){
            //特殊处理
            int offset=flag>7?1:0;
            char c=(flag-2)*3+offset+i+'a';

            path.push_back(c);

            backtracking(n,index+1,digits);

            path.pop_back();
        }

    }
};

 split string

131. Partitioning Palindrome Strings - LeetCode https://leetcode.cn/problems/palindrome-partitioning/

 This question needs to add various ways of splitting into palindrome strings to the final result array, and taking the split position as the index is similar to the combination problem.

Think about passing parameters:

  1. string s
  2. cut index index

Think about the termination condition:
if the cutting position reaches the last position of the string, then break out of the loop and add the number on the path to the result array.

 The ac code is as follows:

class Solution {
public:
    vector<vector<string>> result;
    vector<string> path;
    vector<vector<string>> partition(string s) {
        backtracking(s,0);
        return result;
    }
    bool ishuiwen(const string& s,int start,int end){
        for(int i=start,j=end;i<j;i++,j--){
            if(s[i]==s[j]){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
    void backtracking(const string& s,int index){
        if(index>=s.length()){
            result.push_back(path);
            return;
        }

        for(int i=index;i<s.length();i++){
            if(ishuiwen(s,index,i)){
                string str=s.substr(index,i-index+1);
                path.push_back(str);
            }
            else{
                continue;
            }

            backtracking(s,i+1);

            path.pop_back();
        }



    }
};

IP address segmentation

93. Restoring IP Addresses - LeetCode https://leetcode.cn/problems/restore-ip-addresses/ Similar to the previous question to split the string, this question can also use backtracking, with the split address as the index, add dots No., deleted when backtracking.

Consideration parameters

void backtracking(string& s,int pointNum,int startindex);

s: string

pointNum: the number of points that have been added, when the body is 3, it meets the return conditions

startindex: the index to start selecting at the next split position

terminate return condition

  1. added three dots
  2. The fourth paragraph of data meets the requirements 

Add the isvalid function to judge the cut string:

Whether the string between start-end meets the requirements 

class Solution {
public:
    vector<string> res; 
    vector<string> restoreIpAddresses(string s) {
        res.clear();
        if(s.size()<4 || s.size()>12) return res;
        backtracking(s,0,0);
        return res;
    }

    bool isvalid(const string& s,int start,int end){

        if (start > end) {
            return false;
            }
      
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                        return false;
        }

        int num = 0;
        for (int i = start; i <= end; i++) {
                if (s[i] > '9' || s[i] < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果⼤于255了不合法
            return false;
            }
        }
        return true;
    }
    void backtracking(string& s,int sum,int startindex){
        if(sum==3){
            if(isvalid(s,startindex,s.size()-1)){
                res.push_back(s);
            }
            return;
        }

        for(int i=startindex;i<s.size();i++){

            if(isvalid(s,startindex,i)){
                s.insert(s.begin()+i+1,'.');
                backtracking(s,sum+1,i+2);
                s.erase(s.begin() + i + 1);
            }else break;

        }   
    }
};

Subset problem

Subset

78. Subsets - LeetCode https://leetcode.cn/problems/subsets/

 Splitting and combining is to find the value of the leaf nodes of the backtracking tree, while the subset problem is to collect all the nodes in the tree.

The traversal of for starts from startindex to the end of the array

class Solution {
public:
    vector<vector<int>> res;
    vector<int> vec;
    vector<vector<int>> subsets(vector<int>& nums) {

        backtracking(nums,0);
        return res;
    }

    void backtracking(vector<int>& nums,int index){
      
        res.push_back(vec);
        

        for(int i=index;i<nums.size();i++){
            vec.push_back(nums[i]);
            backtracking(nums,i+1);
            vec.pop_back();
        }
    }
};

 increasing subsequence

491. Increasing Subsequences - LeetCode https://leetcode.cn/problems/non-decreasing-subsequences/This question is also to find subsequences, but what is needed is increasing subsequences, so it needs to be judged, and at the same time Duplication is not allowed, so the result is deduplicated, and set is added to deduplicate the elements of each layer

Consideration parameters

void backtracking(vector<int>& nums,int index);

index is the index position traversed

Termination condition

The title requires two elements or more, and the repeated two elements are used as a special sequence 

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        res.clear();
        path.clear();
        backtracking(nums, 0);
        return res;
    }


    void backtracking(vector<int>& nums,int index){
        if(path.size()>1){
            res.push_back(path);
        }
        //对每层的结果进行去重
        unordered_set<int> uset;
        for(int i=index;i<nums.size();i++){

            if ((!path.empty() && nums[i] < path.back())
                || uset.find(nums[i]) != uset.end()) {
                continue;
                }

            uset.insert(nums[i]);
            path.push_back(nums[i]);

            backtracking(nums,i+1);

            path.pop_back();
        }
    }
};

reschedule

332. Reconstruct itinerary - LeetCode https://leetcode.cn/problems/reconstruct-itinerary/

The label of this question is deep search, but deep search contains the idea of ​​backtracking.

Think about this question:

        We first need to map the corresponding relationship given into a form similar to the adjacency list, and store the reachable relationship between them

        Need to output alphabetically sorted results first

        When backtracking, the final return condition (termination condition) is set

The first problem is to map the reachable relationship. An airport can reach multiple airports, so it can be one-to-one or one-to-many, so use unordered_map<string,multiset<string>> to store related information ,It is also possible to use map, map maintains the order of storing data, and we can also use map<string,unordered_map<string,int>> map to store data

Because the map will sort the airports that can be reached, it can be guaranteed that the airports sorted first will be traversed every time it is traversed.

Consider the condition of the end, an airport, can be a place of departure as well as a place of arrival

Then the number of airports required is the number of tickets plus one

if(ticketnum+1=result.size()); 

class Solution {
public:
    vector<string> result;
    unordered_map<string,map<string,int>> targets;
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear();
        for(const vector<string>& vec:tickets){
            targets[vec[0]][vec[1]]++;
        }
        result.push_back("JFK");
        backtracking(tickets.size());
        return result;
    }


    bool backtracking(int ticketNum){
        if(ticketNum+1==result.size()){
            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;//如果找到了一条路线,就是答案,立即返回
                target.second++;
                result.pop_back();
            }
        }

        return false;
    }
};

n queen problem

51. N Queens - LeetCode https://leetcode.cn/problems/n-queens/

The n queens problem requires us to give all possible permutations and backtrack on a two-dimensional array, such as the backtracking tree below.

The width of the traceback is the length of each column, and the depth is the number of rows,

The logic to build backtrace:
for(int col=0;col<n;col++){

        processing node

        back(); recursion

        Backtracking, undo processing results

Think about the termination condition. If you traverse to the leaf node, which is the last line, add the result and return

if(row==n){

        result.push_back(chessboard);

        return;

When we think about when, a position can insert chess pieces,

Pieces in the previous row, not in the same column as where they will be inserted, not on the upper left slash and on the upper right slash 

bool isvalid(int row,int col,vector<string>& chessboard,int n){
        for(int i=0;i<row;i++){
            if(chessboard[i][col]=='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;i--,j++){
            if(chessboard[i][j]=='Q') return false;
        }

        return true;
    }

class Solution {
public:

    vector<vector<string>> result;
    void backtracking(int n,int row,vector<string>& chessboard){

        if(row==n){
            result.push_back(chessboard);
            return;
        }

        for(int i=0;i<n;i++){
            if(isvalid(row,i,chessboard,n)){
                chessboard[row][i] = 'Q';
                
                backtracking(n,row+1,chessboard);

                 chessboard[row][i] = '.';  
            }

        }
    }

    bool isvalid(int row,int col,vector<string>& chessboard,int n){
        for(int i=0;i<row;i++){
            if(chessboard[i][col]=='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;i--,j++){
            if(chessboard[i][j]=='Q') return false;
        }

        return true;
    }
    vector<vector<string>> solveNQueens(int n) {

        vector<string> vec(n,string(n, '.'));
        backtracking(n,0,vec);
        return result;
    }
};

Guess you like

Origin blog.csdn.net/qq_53633989/article/details/130450890