球球速刷LC-回溯

目录

    • 回溯解题框架
    • 组合问题
          • 求子集1
          • 求子集2
          • 求组合
    • 排列问题
          • 排列1
          • 排列2
    • 组合排列问题应用
          • 组合之和1
          • 组合之和2
          • 组合数之和3
          • 摆牙签
    • 状态与选择
          • 回文字符串分割
          • N皇后问题1
          • N皇后2
          • 恢复IP地址
          • 括号生成问题
          • 删除括号问题
          • 添加括号的方式
          • 解数独

阅读文章

回溯解题框架

组合问题

求子集1
class Solution {
    vector<vector<int>>result;
    void backTrack(vector<int>&can,vector<int>&curr_set,int start)
    {
        result.push_back(curr_set);        
        //代表一轮选择,其选择第一个数为start
        for(int i=start;i<can.size();++i){
             curr_set.push_back(can[i]);
              //每一个数字只能用一次,因此下一轮起始点为i+1
             backTrack(can,curr_set,i+1);
             curr_set.pop_back();
        }
    }    
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int>curr;
        backTrack(nums,curr,0);
        return result;        
    }
};
求子集2

注意对防止方案重复的处理

class Solution {
    
    vector<vector<int>>result;
    void backTrack(vector<int>&can,vector<int>&curr_set,int start)
    {
        result.push_back(curr_set);        
        //代表一轮选择,其选择第一个数为start
        for(int i=start;i<can.size();++i){
            
            //防止方案重复,当前轮选取时,起始第一个数不重复
            if(i>start && can[i]==can[i-1] ) continue;
            
             curr_set.push_back(can[i]);
              //每一个数字只能用一次,因此下一轮起始点为i+1
             backTrack(can,curr_set,i+1);
             curr_set.pop_back();
        }
    }  
    
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int>curr;
        backTrack(nums,curr,0);
        return result;
    }
};
求组合
class Solution {
    vector<vector<int>>result;
    void backTrack(vector<int>&can,vector<int>&curr_set,int start,int k)
    {
       if(curr_set.size()==k){
           result.push_back(curr_set); 
           return;
       }else if(curr_set.size()>k){
           return;
       }    
        //代表一轮选择,其选择第一个数为start
        for(int i=start;i<can.size();++i){
             curr_set.push_back(can[i]);
              //每一个数字只能用一次,因此下一轮起始点为i+1
             backTrack(can,curr_set,i+1,k);
             curr_set.pop_back();
        }
    }  
    
    
public:
    vector<vector<int>> combine(int n, int k) {
        vector<int>nums;
        for(int i=1;i<=n;++i){
            nums.push_back(i);
        }
        vector<int>curr;
        backTrack(nums,curr,0,k);
        return result;  
    }
};

排列问题

排列1

全排列与组合的区别是,所有数字都要被选上,有顺序的区别,所以需要
用一个辅助数组去辨别当前未被选中的元素

class Solution {
    vector<vector<int>>result;
    vector<bool>use;
    void backTrack(vector<int>&can,vector<int>&curr_set,int start)
    {
        if(curr_set.size() == can.size()){
            result.push_back(curr_set);
            return;
        }
        //代表一轮选择,其选择第一个数为start
        for(int i=0;i<can.size();++i){
            if(!use[i]){ 
               use[i]=true;
               curr_set.push_back(can[i]);
              //每一个数字只能用一次,因此下一轮起始点为i+1
               backTrack(can,curr_set,i+1);
               curr_set.pop_back();
               use[i]=false;
            }
        }
    } 
    
    
public:
    vector<vector<int>> permute(vector<int>& nums) {
        //sort(nums.begin(),nums.end());
        vector<int>curr;
        use.resize(nums.size(),false);
        backTrack(nums,curr,0);
        return result; 
    }
};
排列2

注意方案去重的方法

class Solution{
    vector<vector<int>>result;
    vector<bool>use;
    //此处防止方案重复方法是,排序后,相同元素挨着。
    //如果当前元素与上一个元素相同,且上一个元素未使用,则当前方案为重复方案,跳过
    void backTrack(vector<int>&can,vector<int>&curr_set,int start)
    {
        if(curr_set.size() == can.size()){
            result.push_back(curr_set);
            return;
        }
        //代表一轮选择,其选择第一个数为start
        for(int i=0;i<can.size();++i){
            if(!use[i]){ 
               //去除重复方案
               if(i>0 && can[i]==can[i-1] && use[i-1]==false) continue;
                                
               use[i]=true;
               curr_set.push_back(can[i]);
              //每一个数字只能用一次,因此下一轮起始点为i+1
               backTrack(can,curr_set,i+1);
               curr_set.pop_back();
               use[i]=false;
            }
        }
    } 
    
    
    
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int>curr;
        use.resize(nums.size(),false);
        backTrack(nums,curr,0);
        return result;
    }
};

组合排列问题应用

组合之和1
class Solution {
private:
    
vector<vector<int>>result;
    
void backTrack(vector<int> &candidates ,vector<int>&currSet, int target,int currSum,int start)
{
    //从决策树角度来讲
    //第0个数一定选择,再从0...N里面选择剩下数  [因为每一种数都可以重复]
    //第1个数一定选择,再从1...N里面选择省剩下数
    for(int i = start; i < candidates.size() ;i++)
    {
        if(currSum + candidates[i] == target)
        {
            currSet.push_back(candidates[i]);
            result.push_back(currSet);
            currSet.pop_back();
        }
        else if(currSum + candidates[i] < target)
        {
             currSet.push_back(candidates[i]);
             backTrack(candidates,currSet,target,currSum+candidates[i],i);//由于可以重复,下一轮依然从i开始
             currSet.pop_back();
        }
        else break;
    }
} 
    
    
    
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        
        vector<int>curr;
        backTrack(candidates,curr,target,0,0);
        return result;
    }
};
组合之和2

注意如何防止方案重复
注意每个数只能用一次

class Solution {
    vector<vector<int>>result;
    void backTrack(vector<int>&can,int target,vector<int>&curr_set,int curr_sum,int start)
    {
        //假设一共选取了K次,达到目标。则一共要调用backTrack K轮
        //设第1轮的选取起始点为start ,依次遍历start可能的值。则为了每种数最多拿一次,下一轮选取起始点为start+1位置
        //此外,为了防止方案重复,当回到第1轮时,如果下一个遍历的start值与上一次遍历值相等,则应该跳过。
        
        
        //代表一轮选择,其选择起始点为start
        for(int i=start;i<can.size();++i){
            if(i>start && can[i-1]==can[i]) continue;//跳过同一轮选取时重复的起点数
            
            if(can[i]+curr_sum==target){
                curr_set.push_back(can[i]);
                result.push_back(curr_set);
                curr_set.pop_back();
            }else if(can[i]+curr_sum<target){
                  curr_set.push_back(can[i]);
                //每一个数字只能用一次,因此下一轮起始点为i+1
                  backTrack(can,target,curr_set,curr_sum+can[i],i+1);
                  curr_set.pop_back();
            }else{
                break;
            }
        }
    }
    
    
    
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        vector<int>curr;
        backTrack(candidates,target,curr,0,0);
        return result;
        
    }
};
组合数之和3

原理与组合数之和2一致。

class Solution {
    vector<vector<int>>result;
    void backTrack(vector<int>&can,int target,vector<int>&curr_set,int curr_sum,int start,int k)
    {
        //假设一共选取了K次,达到目标。则一共要调用backTrack K轮
        //设第1轮的选取起始点为start ,依次遍历start可能的值。则为了每种数最多拿一次,下一轮选取起始点为start+1位置
        //此外,为了防止方案重复,当回到第1轮时,如果下一个遍历的start值与上一次遍历值相等,则应该跳过。
        
        
        //代表一轮选择,其选择起始点为start
        for(int i=start;i<can.size();++i){            
            if(can[i]+curr_sum==target && curr_set.size()==(k-1)){
                curr_set.push_back(can[i]);
                result.push_back(curr_set);
                curr_set.pop_back();
            }else if(can[i]+curr_sum<target && curr_set.size()<k){
                  curr_set.push_back(can[i]);
                //每一个数字只能用一次,因此下一轮起始点为i+1
                  backTrack(can,target,curr_set,curr_sum+can[i],i+1,k);
                  curr_set.pop_back();
            }else{
                break;
            }
        }
    }
    
    
    
public:
    vector<vector<int>> combinationSum3(int k, int target) {
        vector<int>candidates={1,2,3,4,5,6,7,8,9};
        vector<int>curr;
        backTrack(candidates,target,curr,0,0,k);
        return result;        
    }
};
摆牙签

相当于找4轮组合,每个组合的和相等,等于sum/4。
因此状态有两种,
一种是当前轮组合还没找完,需要继续找。
另一种是当前轮组合已经找完,需要找下一轮。

class Solution {

    bool findSolve=false;
    int len=0;
    vector<bool>visit;
    void splitSum(unsigned int curr_target,int k,int start_pos,vector<int>& nums){
        //前3轮 数字都找到,如果剩下还有数字,其和必然为sum/4
        if(k==1){
            findSolve = true;
        }else{
            for(int i=start_pos;i<nums.size();++i){            
                if(visit[i]==true) continue;
                
                //第K边 sum 目标满足,从0开始寻找下一边的摆放方式
                if(nums[i]==curr_target ){
                    visit[i]=true;
                    splitSum(len,k-1,0,nums);
                    visit[i]=false;
                    if(findSolve) return;
                    //继续找第K轮 sum的元素
                }else if(nums[i]<len){
                    visit[i]=true;
                    splitSum(curr_target-nums[i],k,i+1,nums);
                    visit[i]=false;
                    if(findSolve) return;
                }else if(nums[i]>len){
                    break;
                }
                if(findSolve) return;
            }
        }
        return;
    }
    
    
public:
    bool makesquare(vector<int>& nums) {
        if(nums.size()<4)return false;
        unsigned int sum=0;
        for(auto i:nums) sum+=i;
        
        if(sum<=0) return false;
        
        if(sum%4 !=0) return false;
        
        sort(nums.begin(),nums.end());
        if(nums.back()>(sum/4)) return false;
        
        len=sum/4;
        visit.resize(nums.size(),false);
        splitSum(len,4,0,nums);    
        return findSolve;
        
    }
};

状态与选择

回文字符串分割

分割得到第一个可用回文串后,递归回溯剩余串

class Solution {
   bool isPalindrome(string s, int low, int high){
          while(low < high)  if(s.at(low++) != s.at(high--)){
            return false;  
          }   
          return true;
   }
   vector<vector<string>>result;
   void backtrack(vector<string> & curr, string s){
      if(s.empty()){
          result.push_back(curr);
          return;
      }
      for(int i = 0; i < s.length(); i++){
         if(isPalindrome(s, 0, i)){
            curr.push_back(s.substr(0, i + 1));
            backtrack(curr,s.substr(i+1,s.size()));
            curr.pop_back();
         }
      }
   }

    
public:
    vector<vector<string>> partition(string s) {
        vector<string>curr;
        backtrack(curr,s);
        return result;
    }
};
N皇后问题1

按行进行选择,对当前行可行位置依次遍历时,并迭代进入下一行。

class Solution {

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
    // 触发结束条件
    if (row == board.size()) {
        res.push_back(board);
        return;
    }

    int n = board[row].size();
    for (int col = 0; col < n; col++) {
        // 排除不合法选择
        if (!isValid(board, row, col)) 
            continue;
        // 做选择
        board[row][col] = 'Q';
        // 进入下一行决策
        backtrack(board, row + 1);
        // 撤销选择
        board[row][col] = '.';
    }
}
    
/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
    int n = board.size();
    // 检查列是否有皇后互相冲突
    for (int i = 0; i < n; i++) {
        if (board[i][col] == 'Q')
            return false;
    }
    // 检查右上方是否有皇后互相冲突
    for (int i = row - 1, j = col + 1; 
            i >= 0 && j < n; i--, j++) {
        if (board[i][j] == 'Q')
            return false;
    }
    // 检查左上方是否有皇后互相冲突
    for (int i = row - 1, j = col - 1;
            i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] == 'Q')
            return false;
    }
    return true;
}

vector<vector<string>> res;
public:

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
    // '.' 表示空,'Q' 表示皇后,初始化空棋盘。
    vector<string> board(n, string(n, '.'));
    backtrack(board, 0);
    return res;
}

};
N皇后2

是1的简化版,代码略。

求第K个排列序列
注意n个数的全排列数目为n!个,由此可以计算得到第k个排列时的字母顺序。

class Solution {
    string ret;
    unsigned int jiecheng(unsigned int m){
        unsigned int ret=1;
        for(int i=1;i<=m;++i){
            ret=ret*i;
        }
        return ret;
    }
    
    void generate1(vector<int>&canditates,vector<int>&curr,int&k){
        if(canditates.size()>1){
            unsigned int curr_pos=(k-1)/jiecheng(canditates.size()-1);
            curr.push_back(canditates[curr_pos]);         
            k=k-curr_pos*jiecheng(canditates.size()-1);
            canditates.erase(canditates.begin()+curr_pos);
            generate1(canditates,curr,k);
        }else{
            curr.push_back(canditates[0]);
              ret.clear();
              for(auto i:curr){
                ret.push_back((char)('0'+i));
              }            
              return ;
        }
    }
    
    
public:
    string getPermutation(int n, int k) {
        if(n<1 ||n>9 || k<1) return "";

        vector<int>curr;
        vector<int>canditates;
        for(int i=1;i<=n;++i){
            canditates.push_back(i);
        }
        generate1(canditates,curr,k);
        return ret;        
    }
};
恢复IP地址

类似于切割回文字符串,先从当前串中找到一个有效IP字段,在进一步对剩余串迭代。
注意成功条件是得到4个有效字段,且剩余串长度为0.

class Solution {
    
    vector<string>result;
    
    
    bool isValidIPField(string s){
        if(s.empty()) return false;
        if(s[0]=='0'){
            if(s.size()==1) return true;
            else return false;
        }
        
        if(s.size()==1) return s[0]>='0' && s[0]<='9';
        if(s.size()==2){
            int k=(s[0]-'0')*10+(s[1]-'0');
            return k>=0&&k<=255;
        }
        
        if(s.size()==3){
            int k=(s[0]-'0')*100+(s[1]-'0')*10+(s[2]-'0');
            return k>=0&&k<=255;
        }
        return false;
    }
    
    void backtrack(string s,vector<string>&curr){
        if(curr.size()==4 && s.empty()){
            string s;
            for(auto c:curr){
                s+=c+".";
            }
            s.resize(s.size()-1);
            result.push_back(s);
            return;
        }else if(curr.size()>4){
            return;
        }
        
        for(int i=0;i<s.size();++i){
            if(isValidIPField(s.substr(0,i+1))){
                curr.push_back(s.substr(0,i+1));
                backtrack(s.substr(i+1,s.size()),curr);
                curr.pop_back();
            }
        }
    }
    
    
    
public:
    vector<string> restoreIpAddresses(string s) {
        vector<string>curr;
        backtrack(s,curr);
        return result;
    }
};
括号生成问题

参考文章 生成括号

删除括号问题
class Solution {    
    //本题最大难点是确定 左右括号的不平衡数目,即至少多少左右括号需要被分别删除
    void helper(string&s,int index,string path,int left,int right,int pair,set<string>&ret){
        //结束条件
        if(left==0&&right==0&&pair==0&&index==s.size()){
            ret.insert(path);
            return;
        }else{
           if(index>=s.size()) return;
            
           if(s[index]=='('){
               if(left>0){
                   //删除当前(
                   helper(s,index+1,path,left-1,right,pair,ret);
               }
               //保留当前左括号
               helper(s,index+1,path+"(",left,right,pair+1,ret);
           }else if(s[index]==')'){
               if(right>0){
                   //删除当前右括号
                   helper(s,index+1,path,left,right-1,pair,ret);
               }
               //保留当前右括号
               if(pair>0){
                   helper(s,index+1,path+")",left,right,pair-1,ret);
               }
           }else{
               path.push_back(s[index]);
               helper(s,index+1,path,left,right,pair,ret);
           }
        }
    }
 
public:
    vector<string> removeInvalidParentheses(string s) {
        int left_more=0;
        int right_more=0;
        
        //1.确定左右括号的不平衡数目
        for(auto i:s){
            if(i=='(')++left_more;
            else if(i==')'){
                if(left_more>0)--left_more;
                else ++right_more;
            }
        }

        
        set<string>se;
        helper(s,0,"",left_more,right_more,0,se);
        
        vector<string>ret={se.begin(),se.end()};
        
       return ret;
    }
};
添加括号的方式

数独求解
参考文章

解数独

你可能感兴趣的:(leetcode)