专题:回溯算法专题(完结)

发布于:2024-10-12 ⋅ 阅读:(12) ⋅ 点赞:(0)

回溯四部曲
1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
2.确定遍历顺序(有返回值接的需要接住)
3.确定结束条件(注意是否存在中途直接return)
4.确定单层循环逻辑

1.组合

class Solution {
    vector<vector<int>>  result;
    vector<int>  path;
    void backtracking(int n,int k,int startindex){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
        if(path.size()==k){
            result.push_back(path);
        return;
        }

        for(int i =startindex;i<=n;i++){
            path.push_back(i);
            backtracking(n,k,i+1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combine(int n, int k) {
      backtracking(n,k,1);
      return result;
    }
};

当然也有剪枝

for(int i =startindex;i<=n;i++){

剪枝思考过程 当遍历到i的时候 可以发现已经有path.size()个元素了 还缺 k-path.size()个元素 1…n的时候 也就是从后到前至少有 k-path.size()的元素的才行 因此值是 n-(k-path.size())+1

        for(int i =startindex;i<= n-(k-path.size())+1;i++){
            path.push_back(i);
            backtracking(n,k,i+1);
            path.pop_back();
        }

2.组合总和III

class Solution {
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n,int k,int currentsum,int startindex){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
        
        // cout<<"startindex:"<<startindex<<"   currentsum:   "<<currentsum<<endl;
        if(path.size()==k){
            if(currentsum == n){
              result.push_back(path);
            }
           return;
        }
       //  下面用了剪枝 原本是 for(int i=startindex;i<=9;i++){
      for(int i=startindex;i<=9-(k-path.size())+1;i++){
        currentsum = i + currentsum;
        path.push_back(i);
        backtracking(n,k,currentsum,i+1);
        path.pop_back();
        currentsum = currentsum - i;
      }
    }
public:
    vector<vector<int>> combinationSum3(int k, int n) {
      backtracking(n,k,0,1);
      return result;
    }
};

3.电话号码的字母组合

class Solution {
public:
    vector<string> result;
    string path;

    unordered_map <char, string>  mymap{
        {'2', "abc"},
        {'3', "def"},
        {'4', "ghi"},
        {'5', "jkl"},
        {'6', "mno"},
        {'7', "pqrs"},
        {'8', "tuv"},
        {'9', "wxyz"},
        };
    void backtracking(string digits,int index){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
        if(index ==digits.size()){
          result.push_back(path);
          return;
        }
        string item = mymap[digits[index]];
        for(int i =0;i<item.size();i++){
            char zimu = item[i];
            path.push_back(zimu);
            backtracking(digits, index+1);
            path.pop_back();

        }
    }
    vector<string> letterCombinations(string digits) {
      if(digits=="") return {};
      backtracking(digits, 0);
      return result;
    }
};

4.组合总和

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    void backtracking(vector<int>& candidates, int target,int startindex,int currentsum){
        // 剪枝
        if(currentsum>target){
            return;
        }
        if(currentsum == target){
        result.push_back(path);
        return;
        }
        // cout<<"currentsum:"<<currentsum<<" value:"<<candidates[startindex]<<endl;
        for(int i= startindex;i<candidates.size();i++){
            currentsum = currentsum + candidates[i];
            // 这里也可以直接剪枝 效果比上面剪枝要好 因为不用进入递归函数内部
            // if(currentsum>target){
            //     currentsum = currentsum - candidates[i];
            //     continue;
            // }
            path.push_back(candidates[i]);

            backtracking(candidates,target,i,currentsum);

            path.pop_back();
            currentsum = currentsum - candidates[i];
        }
    }
    
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates,target,0,0);
        return result;
    }
};

5.组合总和II

方法一:先排序 用uses去重数层

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
// vector<bool> used; // 下面可以直接赋值
void backtracking(vector<int>& candidates, int target,int currentsum,int startindex,vector<bool>& used){
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    if(currentsum>=target){
    if(currentsum==target){
        result.push_back(path);
    }
    return;
    }
    // cout<<"currentsum:"<<currentsum<<"  startindex:"<<startindex<<endl;
    for(int i=startindex;i<candidates.size();i++){
    // 树层去重
    if(i-1>=0 &&candidates[i]==candidates[i-1]&&used[i-1]==false){
        continue;
    }
    used[i]=true;
    path.push_back(candidates[i]);
    currentsum=currentsum+candidates[i];
    backtracking(candidates,target,currentsum,i+1,used);
    currentsum=currentsum-candidates[i];
    path.pop_back();
    used[i]=false;
  }
}

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

方法二:先排序 用startindex去重数层

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target,int currentsum,int startindex){
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    if(currentsum>=target){
    if(currentsum==target){
        result.push_back(path);
    }
    return;
    }
    // cout<<"currentsum:"<<currentsum<<"  startindex:"<<startindex<<endl;
    for(int i=startindex;i<candidates.size();i++){
    // 树层去重
    if(i>startindex &&candidates[i]==candidates[i-1]){
        continue;
    }
    path.push_back(candidates[i]);
    currentsum=currentsum+candidates[i];
    backtracking(candidates,target,currentsum,i+1);
    currentsum=currentsum-candidates[i];
    path.pop_back();
  }
}

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0);
        return result;
    }
};

方法三:不排序 直接用set查询该数层之前是否使用过


class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target,int currentsum,int startindex){
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    if(currentsum>=target){
    if(currentsum==target){
        result.push_back(path);
    }
    return;
    }
    // cout<<"currentsum:"<<currentsum<<"  startindex:"<<startindex<<endl;
    unordered_set<int> myset;
    for(int i=startindex;i<candidates.size();i++){
    if(myset.count(candidates[i])>0){
        continue;
    }
    myset.insert(candidates[i]);
    path.push_back(candidates[i]);
    currentsum=currentsum+candidates[i];
    backtracking(candidates,target,currentsum,i+1);
    currentsum=currentsum-candidates[i];
    path.pop_back();
    // 要特别注意myset不需要回溯 这个是树层的的变量 同意树层需要用这个判断的
    // myset.erase(candidates[i]);
  }
}

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0);
        return result;
    }
};

6.分割回文串

class Solution {
 

public:
    vector<vector<string>> result;
    vector<string> path;
    bool IsValid(string newstr) {
        int i=0;
        int j = newstr.size()-1;
        while(i<=j) {
            if(newstr[i]!=newstr[j]){
              return false;
            }
            i++;
            j--;
        }
        return true;
    }

    void backtracking(string s, int startindex) {
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
        if(startindex == s.size()){
            result.push_back(path);
            return;
        }
      for(int i=startindex;i<s.size();i++){
        string newstr=s.substr(startindex,i-startindex+1);
        if(!IsValid(newstr)){
           continue;
        }
        path.push_back(newstr);

        backtracking(s,i+1);

        path.pop_back();
      }
    }

    vector<vector<string>> partition(string s) {
       backtracking(s,0);
       return result;
    }
};

7.复原IP地址

这里终止条件

    if(doccount >=4){
        if(startindex == s.size() && doccount==4){
            cout<<"path:"<<path<<endl;
            result.push_back(path.substr(0,path.size()-1));
        }
        return;
    }

完整代码

class Solution {

public:
vector<string> result;
// string path; 添加字符串后 还得再次移除 因此不用全局 直接用临时的
bool IsValid(string newstr){
    if(newstr[0]=='0'&&newstr.size()>1){
        return false;
    }
    int intValue = stoi(newstr);
    if(intValue>255){
        return false;
    }
    return true;
}
void backtracking(string s,int startindex,string path,int doccount){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
    if(doccount >=4){
        if(startindex == s.size() && doccount==4){
            cout<<"path:"<<path<<endl;
            result.push_back(path.substr(0,path.size()-1));
        }
        return;
    }

    for(int i=startindex;i<s.size();i++){
        string newstr = s.substr(startindex,i-startindex+1);
        if(!IsValid(newstr)){
            break;
        }
        doccount++;
        //path+(newstr)+"."不会改变本层的值 而用path.append(newstr).append(".");  这种会改变本层path的值 
        backtracking(s,i+1, path+(newstr)+".",doccount);
        doccount--;
    }
}
    vector<string> restoreIpAddresses(string s) {
      backtracking(s,0,"",0);
      return result;
    }
};

8.子集

求子集的时候 注意收获结果的位置是 二叉树中的每个节点位置 并且终止条件可以不写 因为即使不写终止条件也进入不了for的循环中

class Solution {

public:
vector<vector<int>> result;
vector<int> path{};
void backtracking(vector<int>& nums, int startindex){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
    result.push_back(path);
    // 下面这个终止条件在求子集问题中没有必要 因为进入不了for的循环中
    // if(path.size()>=nums.size()){
    //     return;
    // }
    for(int i=startindex;i<nums.size();i++){
      path.push_back(nums[i]);

      backtracking(nums,i+1);

      path.pop_back();
    }
}
    vector<vector<int>> subsets(vector<int>& nums) {
      backtracking(nums,0);
      return result;
    }
};

9.子集II

有元素存在重复需要树层剪枝 这里使用 先排序然后用used判断进行剪枝

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path{};
    void backtracking(vector<int>& nums,int startindex,vector<int> & used){
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    result.push_back(path);
    for(int i=startindex;i<nums.size();i++){
        if(i-1>=0&&nums[i]==nums[i-1]&&used[i-1]==false){
            continue;
        }
        path.push_back(nums[i]);
        used[i]=true;
        backtracking(nums,i+1,used);
        used[i]=false;
        path.pop_back();
    }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int>  used(nums.size(),false);
        backtracking(nums,0,used);
        return result;
    }
};

10.递增子序列

1.这里需要注意树枝去重 并且不能按照题目的意思不能使用排序 因此去重使用set查询是否在同一层去重

        // 树层去重
        if(myset.count(nums[i])>0){
            continue;
        }
        myset.insert(nums[i]);

2如果不满足递增 应该continue .

        if(!path.empty() && path.back()>nums[i]){
          continue;
        }

完整代码

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
void backtracking(vector<int>& nums,int startindex){
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    if(path.size()>=2){
      result.push_back(path);
    }
    unordered_set<int> myset{};
    for(int i=startindex;i<nums.size();i++){
        // 树层去重
        if(myset.count(nums[i])>0){
            continue;
        }
        myset.insert(nums[i]);

        if(!path.empty() && path.back()>nums[i]){
          continue;
        }

        path.push_back(nums[i]);
        backtracking(nums,i+1);
        path.pop_back();
    }
}
    vector<vector<int>> findSubsequences(vector<int>& nums) {
      backtracking(nums,0);
      return result;
    }
};

11.全排列

排列问题 不能依靠startindex了 只能靠used看判断是否使用过避免取重复数字了

class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums,vector<bool>& used){
    // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
    // 2.确定遍历顺序(有返回值接的需要接住)
    // 3.确定结束条件(注意是否存在中途直接return)
    // 4.确定单层循环逻辑
    if(path.size()==nums.size()){
        result.push_back(path);
        return;
    }

    for(int i = 0;i<nums.size();i++){
        if(used[i] == true) {
            continue;
        }
        used[i]=true;
        path.push_back(nums[i]);
        backtracking(nums,used);
        path.pop_back();
        used[i]=false;
    }

}
    vector<vector<int>> permute(vector<int>& nums) {
      vector<bool> used(nums.size(),false);
      backtracking(nums, used);
      return result;

    }
};

12.全排列 II

注意这里树层去重和全排列避免取到相同位置的数字 可以直接共用一个used

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums,vector<bool>& used){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
        if(path.size()==nums.size()){
            result.push_back(path);
            return;
        }
        for(int i =0;i<nums.size();i++){
            if(i-1>=0&&nums[i]==nums[i-1]&&used[i-1]==false){
                continue;
            }
            if(used[i]==true){
                continue;
            }
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            path.pop_back();
            used[i]=false;
        }
    }

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        backtracking(nums, used);
        return result;
    }
};

13.重新安排行程

14.N皇后

这里可以递归内部控制列填的位置 递归透传行(深度)的大小 当行等于棋盘的大小的时候 可以认为 找到了符合条件的棋盘方法 也就是递归结束
注意下vector初始化

        vector<string> temp(n, string(n, '.'));
        chessboard = temp;

完整代码

class Solution {
    vector<vector<string>> result;
    vector<string> chessboard;
    bool IsValid(vector<string> &chessboard, int n,int low,int col){
        // 检查列 也就是列固定 行变化
        for(int i = low;i>=0;i--){
          if(chessboard[i][col] == 'Q'){
            return false;
          }
        }
        // 不用检查行 因为backtracking已经保证行只能有一个

        // 检查斜角45°
        for(int i=low,j=col;i>=0&&j<=n;i--,j++){

          if(chessboard[i][j] == 'Q'){
            return false;
          }
        }

        // 检查斜角135°
        for(int i=low,j=col;i>=0&&j>=0;i--,j--){
          if(chessboard[i][j] == 'Q'){
            return false;
          }
        }

        return true;
    }
    void backtracking( int n,int startlow){
        // 1.确定是否需要返回值(和题目的递归函数函数是否有返回值无关)
        // 2.确定遍历顺序(有返回值接的需要接住)
        // 3.确定结束条件(注意是否存在中途直接return)
        // 4.确定单层循环逻辑
        if(startlow == n){
          result.push_back(chessboard);
          return;
        }
        for(int col=0;col<n;col++){
            if(!IsValid( chessboard,n, startlow, col)){
                continue;
            }
            chessboard[startlow][col] = 'Q';
            backtracking(n,startlow+1);
            chessboard[startlow][col] = '.';
        }
    }
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<string> temp(n, string(n, '.'));
        chessboard = temp;
        backtracking(n,0);
        return result;

    }
};

15.解数独

注意点 这里题目帮填了一部分 自己也填了一部分 自己填的一部分 一定要检查所在的这整行 以及整列(N皇后是检查自己填的即可 不用检查整列)

bool IsValid(vector<vector<char>> &board,int n,int m,int row,int col,char k){
    // cout<<"row:"<<row<<" col:"<<col<<" k:"<<k<<endl;
    for(int i =  board.size()-1;i>=0;i--){
      if(board[i][col]==k){
        return false;
      }
    }
    for(int j =  board[0].size()-1;j>=0;j--){
      if(board[row][j]==k){
        return false;
      }
    }

    // 九宫格内只能出现一次
    for(int i =  row/3 * 3;i<row/3 * 3+3;i++){
        for(int j =  col/3 * 3;j<col/3 * 3+3;j++){
            if(board[i][j]==k){
            return false;
            }
        }
    }
    return true;
}

完整代码

class Solution {
public:

bool IsValid(vector<vector<char>> &board,int n,int m,int row,int col,char k){
    // cout<<"row:"<<row<<" col:"<<col<<" k:"<<k<<endl;
    for(int i =  board.size()-1;i>=0;i--){
      if(board[i][col]==k){
        return false;
      }
    }
    for(int j =  board[0].size()-1;j>=0;j--){
      if(board[row][j]==k){
        return false;
      }
    }

    // 九宫格内只能出现一次
    for(int i =  row/3 * 3;i<row/3 * 3+3;i++){
        for(int j =  col/3 * 3;j<col/3 * 3+3;j++){
            if(board[i][j]==k){
            return false;
            }
        }
    }
    return true;
}

bool backtracking(vector<vector<char>>& board,int n,int m){
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(board[i][j]!='.'){
                continue;
            }
            for(char k='1';k<='9';k++){
                if(!IsValid(board,n,m,i,j,k)){
                   continue;
                }
                board[i][j]=k;
                if(backtracking(board,n,m)){
                    for(int i =0;i<9;i++){
                        for(int j =0;j<9;j++){
                         cout<<board[i][j];
                        }
                    }
                    return true;
                };
                board[i][j]='.';
            }
            return false;
        }
    }
    return true;

}
    void solveSudoku(vector<vector<char>>& board) {
        int n = board.size();
        int m = board[0].size();
        backtracking(board,n,m);
    }
};