Day22 回溯算法:216.组合总和Ⅲ 17.电话号码的字母组合

115 阅读4分钟

216.组合总和Ⅲ

题目链接:216.组合总和Ⅲ

难度指数:😀😐😕

如果把 组合问题理解了,本题就容易一些了。

最朴素、暴力的想法:k是多少,就嵌套几层for循环

回溯算法也是暴力的方式,和之前嵌套几个for循环的思路是一样的,只不过回溯算法通过递归的方式帮我们控制for循环的嵌套层数。

任何用回溯算法解决的问题,都可以抽象成一个树形结构

本题k控制着树的深度(满足k就行,再往下递归就没意义了),9(因为整个集合就是9个数)就是树的宽度(for循环的范围)。

22.01.png

代码思路:

  • 一维数组path,收集单条路径,符合要求的结果
  • 二维数组result,(符合题目描述要求的结果可能不止一个),把符合题目要求的path都放进result

递归函数的参数和返回值:

本题的递归函数不需要我们返回东西,因为结果都放在全局变量。

确定参数:1️⃣ targetSum,将要求的组合的和; 2️⃣ k; 3️⃣ Sum收集这条路径已有的和 4️⃣ startIndex

最后 targetSumSum 作比较,若相同就是符合题目要求的组合

startIndex ,我们每次递归的时候,会把这次要搜索的起始位置传进来,(最开始调用递归函数的时候,传进去的startIndex是1)

 void backtracking(targetSum, k, Sum, startIndex) {
     //终止条件
     if (path.size() == k) {  //k控制着树的深度
         if (targetSum == Sum) {  //若相等,这个就是一个目标组合
             result.push_back(path);  //放进result
         }
     }
     //单层递归的逻辑
     for (int i = startIndex; i <= 9; i++) {
         Sum += i;
         path.push_back(i);
         backtracking(targetSum, k, Sum, i + 1);
         sum -= i;
         path.pop_back();  //回溯
     }
 }

AC代码: (核心代码模式)

 ​

剪枝:

22.02.png

代码思路:

 void backtracking(targetSum, k, Sum, startIndex) {
     //一个剪枝:
     if (Sum > targetSum) {
         return;
     }
     //终止条件
     if (path.size() == k) {  //k控制着树的深度
         if (targetSum == Sum) {  //若相等,这个就是一个目标组合
             result.push_back(path);  //放进result
         }
     }
     //单层递归的逻辑
     //for循环条件里有另一个剪枝:(对i的边界上的控制)
     for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
         Sum += i;
         path.push_back(i);
         backtracking(targetSum, k, Sum, i + 1);
         sum -= i;
         path.pop_back();  //回溯
     }
 }

for循环条件里有另一个剪枝:(对i的边界上的控制)

有点难理解

如果 k = 2 ,那么 i 只需要遍历到8就可以了。

if (Sum > targetSum) { return; } 也可以放这里:

 void backtracking(targetSum, k, Sum, startIndex) {
     //终止条件
     if (path.size() == k) {  //k控制着树的深度
         if (targetSum == Sum) {  //若相等,这个就是一个目标组合
             result.push_back(path);  //放进result
         }
     }
     //单层递归的逻辑
     for (int i = startIndex; i <= 9; i++) {
         Sum += i;
         path.push_back(i);
         if (Sum > targetSum) {
             return;
         }
         backtracking(targetSum, k, Sum, i + 1);
         sum -= i;
         path.pop_back();  //回溯
     }
 }

AC代码: (核心代码模式)

 class Solution {
 private:
     vector<vector<int>> result;  //存放结果集
     vector<int> path;  //存放符合条件的结果
     void backtracking(int targetSum, int k, int sum, int startIndex) {
         //一个剪枝:
         if (sum > targetSum) {
             return;
         }
         //终止条件
         if (path.size() == k) {  //k控制着树的深度
             if (targetSum == sum) {  //若相等,这个就是一个目标组合
                 result.push_back(path);
             }
         }
         //单层递归的逻辑
         //for循环里有另一个剪枝:(对i的边界上的控制)
         for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
             sum += i;
             path.push_back(i);
             backtracking(targetSum, k, sum, i + 1);
             sum -= i;  //回溯
             path.pop_back();  //回溯  
         }
     }
 public:
     vector<vector<int>> combinationSum3(int k, int n) {
         result.clear();
         path.clear();
         backtracking(n, k, 0, 1);
         return result;
     }
 };

17.电话号码的字母组合

题目链接:17.电话号码的字母组合

难度指数:😀😕🙁

本题大家刚开始做会有点难度,先自己思考20min,没思路就直接看题解。

可以用map做映射,也可以用一个二维数组来做映射

例如:输入:"23",抽象为树形结构,如图所示:

22.03.png

这棵树的深度由我们输入数字的个数来控制;树的宽度由每个数字所对应的字母的长度来控制。

收获结果的地方都是在叶子节点

代码思路:

string s; 存放单个结果

vector<string> result; 单个结果放进结果集

注意这个index可不是 77.组合216.组合总和Ⅲ中的 startIndex

这个 index 是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index 也表示树的深度。

index 仅仅是表示在递归中,传入的字符串遍历到哪个数字了。

(上一道题的 startIndex 是告诉我们:我们之前已经收获了哪些元素,避免我们得到重复的组合,)

而本题是在两个集合中,将两个集合的每个元素做一个组合。(本题并不需要 startIndex 帮我们做去重的操作 )

 string s;
 vector<string> result;
 void backtracking(digits, index) {
     //终止条件:
     if (index == digits.size()) {  //说明遍历到头了  (index从0开始)
         result.push_back(s);
         return;
     }
     //单层递归的逻辑:
     int digit = dights[index] - '0';  //eg:字母2变成数字2
     string letters = letterMap[digit];  //取数字对应的字符集
     for (int i = 0; i < letters.size(); i++) {
         s.push_back(letters[i]);
         backtracking(digits, index + 1);
         s.pop_back();  //回溯
     }
 }

AC代码: (核心代码模式)

 class Solution {
 private:
     const string letterMap[10] = {
         "",     //0
         "",     //1
         "abc",  //2
         "def",  //3
         "ghi",  //4
         "jkl",  //5
         "mno",  //6
         "pqrs", //7 
         "tuv",  //8
         "wxyz", //9
     };
 public:
     string s;  //存放单个结果
     vector<string> result;  //将单个结果放进结果集
     void backtracking(const string& digits, int index) {
         //终止条件
         if (index == digits.size()) {  //说明遍历到头了  (注:index从0开始)
             result.push_back(s);
             return;
         }
         //单层递归的逻辑:
         int digit = digits[index] - '0';  //eg:字母2变成数字2
         string letters = letterMap[digit];  //取数字对应的字符集
         for (int i = 0; i < letters.size(); i++) {
             s.push_back(letters[i]);
             backtracking(digits, index + 1);  //递归
             s.pop_back();  //回溯
         }
     }
 public:
     vector<string> letterCombinations(string digits) {
         s.clear();
         result.clear();
         if (digits.size() == 0) {
             return result;
         }
         backtracking(digits, 0);
         return result;
     }
 };