Leetcode刷题笔记Day11:回溯Ⅰ

110 阅读2分钟

回溯基本概念

回溯是递归的副产品,只要有递归就会有回溯

回溯法就是暴力搜索,并不是什么高效的算法,最多在剪枝一下。

回溯算法能解决如下问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

组合

  • 力扣题目链接
  • 我们开始用回溯法解决第一道题目,组合问题
  • 此时大家应该深有体会回溯法的魅力,用递归控制for循环嵌套的数量!
  • 本题我把回溯问题抽象为树形结构,可以直观的看出其搜索的过程:
  • for循环横向遍历,递归纵向遍历,回溯不断调整结果集
  • 朴素版实现:
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path;           // 用来存放符合条件结果
void backtracking(int n, int k, int startIndex){
    // startIndex:记录下一层递归,搜索的起始位置
    // 终止条件
    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();                // 回溯
    }
}
vector<vector<int>> combine(int n, int k) {
    backtracking(n, k, 1);
    return result;
}
  • 剪枝:仅修改i <= n - (k - path.size()) + 1

组合总和III

vector<vector<int>> result;
vector<int> path;
void backtracking(int k, int n, int sum, int startIndex){
    if(path.size()==k && sum==n) {
        result.push_back(path); return;
    }
    for(int i=startIndex; i<=9; i++){
        sum+=i;
        path.push_back(i);
        backtracking(k, n, sum, i+1);
        sum-=i; //回溯
        path.pop_back();
    }
}
vector<vector<int>> combinationSum3(int k, int n) {
    backtracking(k, n, 0, 1);
    return result;
}
  • 剪枝优化:
vector<vector<int>> result;
vector<int> path;
void backtracking(int k, int n, int sum, int startIndex){
    if(sum>n) return;   // 优化1
    if(path.size()==k && sum==n) {
        result.push_back(path); return;
    }
    // 优化2
    for(int i=startIndex; i<=9-(k-path.size())+1; i++){
        sum+=i;
        path.push_back(i);
        backtracking(k, n, sum, i+1);
        sum-=i;         //回溯
        path.pop_back();
    }
}
vector<vector<int>> combinationSum3(int k, int n) {
    backtracking(k, n, 0, 1);
    return result;
}
  • 终极优化版(省略sum):
vector<vector<int>> result;
vector<int> path;
void backtracking(int k, int n, int startIndex){
    if(n<0) return;
    if(path.size()==k && n==0) {
        result.push_back(path); return;
    }
    for(int i=startIndex; i<=9-(k-path.size())+1; i++){
        path.push_back(i);
        backtracking(k, n-i, i+1);
        path.pop_back();
    }
}
vector<vector<int>> combinationSum3(int k, int n) {
    backtracking(k, n, 1);
    return result;
}

电话号码的字母组合

const string letterMap[8]={
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};
vector<string> results;
string path;
void backtracking(const string& digits, int index){
    if(index==digits.size()) {
        results.push_back(path); return;
    }
    int digit=digits[index]-'0';
    string letters=letterMap[digit-2];
    for(int i=0; i<letters.size(); i++){
        path.push_back(letters[i]);
        backtracking(digits, index+1);  // 注意:不是i+1
        path.pop_back();
    }
}
vector<string> letterCombinations(string digits) {
    if(digits.empty()) return results;  // 别忘了这个朴素情况
    backtracking(digits, 0);
    return results;
}

参考资料

[1] 代码随想录

[2] Leetcode题解