回溯算法笔记

什么是回溯

通过探索所有可能的解,找到正确解的算法;如果当前解不满足条件,那么将舍弃该解并返回到上一步;

本质就是一个树形结构,利用深度优先搜索一条路走到黑的特性,不满足条件就舍弃该节点,并放回其父节点;

回溯&递归&贪心

回溯模板

void blacktrack(路径,选择列表){
    if(满足条件){
        保存路径
        return;
    }
    for(选择:选择列表){
        做选择;
        blacktrack(路径,选择列表);
        抛弃刚才的选择;
    }
}

常见回溯算法题

  • 组合总和 https://leetcode-cn.com/problems/combination-sum/
  • 括号生成 https://leetcode-cn.com/problems/generate-parentheses/
  • N皇后 https://leetcode-cn.com/problems/n-queens/
  • 全排列 https://leetcode-cn.com/problems/permutations/
  • ……

回溯题目

电话号码的字母组合

  1. 题目

    给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

    给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

  2. 示例

    输入:digits = “23”
    输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

  3. 要求
    0 <= digits.length <= 4
    digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

  4. 题目地址
    https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

  5. 分析

    1. 用容器将数字与字母对应起来,形成一个键值对;
      2)方法1; 最暴力的方式是逐个用digits[0]中的某一个字母去对应digits[1]中的字母,即双重循环;时间复杂度为,O(n^2),可能会导致超时;
      3) 方法2: 回溯法,当将digits[1]中的字母全部组合后,digits[0]回退到上一步,digits[1]回退到原始;
  6. 代码

     #include<iostream>
     #include<vector>
     #include<unordered_map>
     using namespace std;
     class Solution{
     public: 
         // C++11新特性
         Solution() = default;
         ~Solution() = default;
         unordered_map<char, string> phoneMap = {
                 {'2', "abc"},
                 {'3', "def"},
                 {'4', "ghi"},
                 {'5', "jkl"},
                 {'6', "mno"},
                 {'7', "pqrs"},
                 {'8', "tuv"},
                 {'9', "wxyz"}
    
         }; 
         void backTrack(string digits, unordered_map<char, string> phoneMap, vector<string> &combinations, int index, string tmpCombination){
             //1. 定一个出口条件: digits到头就全部结束了
             if( index == digits.length() ){
                 combinations.push_back(tmpCombination);
                 return;
             }
             char digit = digits[index];
             const string& letter = phoneMap.at(digit);
             //2. 回溯一定是有进出的,使用完之后必须及时抛弃
             for( const char& lett : letter ){
                 tmpCombination.push_back(lett);
                 backTrack(digits, phoneMap, combinations, index+1, tmpCombination);
                 tmpCombination.pop_back();
             }
         }
         vector<string> letterCombinations(string digits){
             vector<string> combinations;
             if( digits.empty() )
                 return combinations;
             //回溯使用深度优先搜索
             backTrack(digits, phoneMap, combinations, 0, "");
             return combinations;
         }
    
     };
     int main()
     {
     Solution so;
     //这里也可以使用键盘输入方式
     vector<string> result = so.letterCombinations("23");
     vector<string>::iterator it = result.begin();
     for(;it != result.end();it++){
         cout<<*it<<endl;
     }
     system("pause");
     return 0;
     }
     
     /*
      结果为: 
         ad
         ae
         af
         bd
         be
         bf
         cd
         ce
         cf
     */
    

剩下的先写代码,后补充

括号生成

void dfsParenthesis(vector<string>& result, int n, int left, int right, string generate){
   //括号左右数量相等;任意前缀左括号>=右括号数量
   if(left>n || right >n  || left<right) return;
   if(left == n && right == n){ // n对括号已完成
      result.push_back(generate);
   }else{
      dfsParenthesis(result, n, left+1,right, generate+"(");
      dfsParenthesis(result, n, left,right+1, generate+")");
   }
}
vector<string> leetcode::generateParenthesis(int n) {
   vector<string> result;
   int left = 0;
   int right = 0;
   string generate = "";
   dfsParenthesis(result, n,left,right,generate);
   return result;
}

组合

void dfsComBine(vector<vector<int>>& result, vector<int> nums, int k , int pos, vector<int>& tmp){
   if(tmp.size() == k){
      result.push_back(tmp);
      return;
   }
   for(int i= pos; i<nums.size();i++){
      tmp.push_back(nums[i]);
      dfsComBine(result, nums, k, i+1, tmp);
      tmp.pop_back();
   }
}
vector<vector<int>>::leetcode combine(int n, int k) {
   vector<vector<int>> result;
   vector<int> nums;
   vector<int> tmp;
   for(int i=1;i<=n;i++)
      nums.push_back(i);
   dfsComBine(result, nums, k, 0, tmp);
   return result;
}

组合总和

void dfsSum(vector<vector<int>>& result, int index, vector<int>& candidates, int target,vector<int>& candidate){
   if(target < 0) return;
   if(target == 0){ result.push_back(candidate); return;}
   //这里还差一个条件     
   for(int i=index; i<candidates.size();i++){
      candidate.push_back(candidates[i]);
      //i不变是因为可以重复取值
      dfsSum(result, i, candidates, target-candidates[i], candidate);
      candidate.pop_back();
   }
   
}
vector<vector<int>> leetcode::combinationSum(vector<int>& candidates, int target) {
   //这里的出口是数字差
   vector<vector<int>> result;
   vector<int> candidate;
   dfsSum(result, 0, candidates, target, candidate);
   return result;
}

组合总和 II

void dfsSum2(vector<vector<int>>& result, int index, vector<int>& candidates, int target,vector<int>& candidate){
   if(target < 0) return;
   if(target == 0){ result.push_back(candidate); return;}
   //这里还差一个条件     
   for(int i=index; i<candidates.size();i++){
      //当前数字不能出现过
      if(candidates[i]<=target){
         if(i>index && candidates[i] == candidates[i-1]) continue;
         candidate.push_back(candidates[i]);
         //这里不可以取重复值
         dfsSum2(result, i+1, candidates, target-candidates[i], candidate);
         candidate.pop_back();
      }
   }
}
vector<vector<int>> leetcode::combinationSum2(vector<int>& candidates, int target) {
   //去重复最简单的就是先排序,否则就需要一个flag去单独定义
   sort(candidates.begin(), candidates.end());
   vector<vector<int>> result;
   vector<int> candidate;
   dfsSum2(result, 0, candidates, target, candidate);
   return result;
}

全排列

void dfsPremute(vector<int>& nums, vector<vector<int>>& result, vector<int>& tmpNum,vector<int>& flagNums){
   if(tmpNum.size() == nums.size()){
      result.push_back(tmpNum);
      return;
   }
   //或者借助单队列执行
   for(int i=0;i<nums.size();i++){
      if(flagNums[i] == 1){continue;} //这里会浪费时间,因为被多次执行了
      else{
         tmpNum.push_back(nums[i]);
         flagNums[i] = 1;
         dfsPremute(nums, result, tmpNum, flagNums);
         flagNums[i] = 0;
         tmpNum.pop_back();  
      }
     
   }
}
vector<vector<int>> leetcode::permute(vector<int>& nums) {
   // 题目给出的是不含重复数字,所以这里就不需要排序啦
   vector<vector<int>> result;
   vector<int> tmp;
   vector<int> flagNums(nums.size(), 0);
   dfsPremute(nums, result, tmp,flagNums);
   return result;
}

全排列 II

子集

void dfsSubsets(vector<vector<int>>& result, int pos, vector<int>& nums, vector<int>& tmp){
   result.push_back(tmp);
   for(int i=pos;i<nums.size();i++){
      tmp.push_back(nums[i]);
      dfsSubsets(result, i+1, nums, tmp);
      tmp.pop_back();
   }
   
}
vector<vector<int>> subsets(vector<int>& nums) {
   vector<vector<int>> result;
   vector<int> tmp;
   dfsSubsets(result, 0, nums, tmp);
   
   /*
   result.push_back({});
   for(int i = 0;i<nums.size();i++){
      int tmpSize  = result.size();
     for(int j=0;j<tmpSize;j++){
         vector<int> tmp(result[j]);
         tmp.push_back(nums[i]);
         result.push_back(tmp);
     }    
    
   }*/
   return result;
}

子集 II

void dfsSubsets(vector<vector<int>>& result, int pos, vector<int>& nums, vector<int>& tmp){
   result.push_back(tmp);
   //一个循环里面,pos是不会改变的
   for(int i=pos;i<nums.size();i++){
      if(i != pos && nums[i] == nums[i-1]) continue;
      tmp.push_back(nums[i]);
      dfsSubsets(result, i+1, nums, tmp);
      tmp.pop_back();
   }
}
vector<vector<int>>::leetcode subsetsWithDup(vector<int>& nums){
   sort(nums.begin(),nums.end());
   vector<vector<int>> result;
   vector<int> tmp;
   dfsSubsets(result, 0, nums, tmp);
   return result;
}

单词搜索

bool dfsSourch(vector<vector<char>>& board, string word, int x, int y, vector<vector<int>>& visited, int pos){
   //1. 找到满足的值 - 出口
   if(pos == word.size()) return true;
   //2. 剔除边界值 - 出口   注意前两个一定要有等号,否则影响最后一个不等式
   if(x>=board.size()|| y>=board[0].size() || x<0 || y<0 || board[x][y] != word[pos]) return false;
  

   //3. 修改标记
   if(visited[x][y] == false){
      visited[x][y] = true;
      //4. 上下左右依次遍历
      if(dfsSourch(board, word, x-1, y, visited, pos+1)
      ||dfsSourch(board, word, x+1, y, visited, pos+1)
      ||dfsSourch(board, word, x, y-1, visited, pos+1)
      ||dfsSourch(board, word, x, y+1, visited, pos+1))
         return true;          
      //5. 回溯前做了什么,回溯后做相反操作
      visited[x][y] = false;
   }
   return false;
}
bool leetcode::exist(vector<vector<char>>& board, string word) {
   //也就是前后左右都要查到,致命问题来了
   int row = board.size();
   int col= board[0].size();
   int totalSize =row*col;
   if(word.length()>totalSize) return false;
   vector<vector<int>> visited(row, vector<int>(col, 0));
   //主要
   for(int i=0;i<row;i++){
      for(int j=0;j<col;j++){
         if(dfsSourch(board, word, i, j, visited, 0)){
            return true;
         }
      }
   }
   return false;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值