首页 > 编程语言 >代码随想录算法训练营Day24 回溯算法|216.组合总和III 17.电话号码的字母组合

代码随想录算法训练营Day24 回溯算法|216.组合总和III 17.电话号码的字母组合

时间:2023-02-25 20:12:19浏览次数:71  
标签:index int sum 随想录 算法 result 回溯 字母组合 path

代码随想录算法训练营

216.组合总和III

题目链接:216.组合总和III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。

总体思路

本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合
本题k相当于树的深度,9(因为整个集合就是9个数)就是树的宽度。
例如 k = 2,n = 4的话,就是在集合[1,2,3,4,5,6,7,8,9]中求 k(个数) = 2, n(和) = 4的组合。
选取过程如图:

回溯三部曲

  • 确定递归参数
    仍然需要一维数组path存放符合的结果,二维数组result来存放结果集
    结果就是一条根节点到叶子节点的路径。
vector<vector<int>> result;//存放结果集
vector<int> pat;//符合条件的结果

接下来需要如下参数:

  • targetSum (int) 目标和,即题目中的n
  • k (int) 题目中要求k个数的集合
  • sum (int) 为已收集元素的总和,也是path里元素的总和
  • startIndex(int) 为下一层for循环搜索的起始位置
    代码如下:
vector<vector<int>> result;//存放结果集
vector<int> pat;//符合条件的结果
void backtracking(int targetSum,int k, int sum, int startIndex)
  • 确定终止条件
    题目中的k就相当于限制了树的深度,当path.size( )和k相等了,就终止。
    此时path里收集到的元素和sum和targetSum(题目中的n)相同了,就result收集结果
    代码如下:
if(path.size()==k){
	if(sum==targetSum) result.push_back(path);
	return;//如果path.size()==k但sum!=targetSum 直接返回
}
  • 单层搜索过程

    处理过程就是path收集每次选区的元素,相当于树形结构的边,sum用来统计path的总和
for(int i=startIndex;i<=9;i++){
    sum+=1;
    path.push_back(i);
    backtracking(targetSum,k,sum,i+1);
    sum-=1;//回溯
    path.pop_back()//回溯
}

总体如下:

class Solution {
private:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    // targetSum:目标和,也就是题目中的n。
    // k:题目中要求k个数的集合。
    // sum:已经收集的元素的总和,也就是path里元素的总和。
    // startIndex:下一层for循环搜索的起始位置。
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return; // 如果path.size() == k 但sum != targetSum 直接返回
        }
        for (int i = startIndex; i <= 9; i++) {
            sum += i; // 处理
            path.push_back(i); // 处理
            backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
            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;
    }
};

剪枝


已选元素总和如果已经大于n(图中数值为4)了,那么往后遍历就没有意义了,直接剪掉。
那么剪枝的地方可以放在递归函数开始的地方,剪枝代码如下:

if (sum > targetSum) { // 剪枝操作
    return;
}

当然这个剪枝也可以放在 调用递归之前,即放在这里,只不过要记得 要回溯操作给做了。

for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
    sum += i; // 处理
    path.push_back(i); // 处理
    if (sum > targetSum) { // 剪枝操作
        sum -= i; // 剪枝之前先把回溯做了
        path.pop_back(); // 剪枝之前先把回溯做了
        return;
    }
    backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
    sum -= i; // 回溯
    path.pop_back(); // 回溯
}

回溯算法:组合问题再剪剪枝 一样,for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。
最后C++代码如下:

class Solution {
private:
    vector<vector<int>> result; // 存放结果集
    vector<int> path; // 符合条件的结果
    void backtracking(int targetSum, int k, int sum, int startIndex) {
        if (sum > targetSum) { // 剪枝操作
            return; // 如果path.size() == k 但sum != targetSum 直接返回
        }
        if (path.size() == k) {
            if (sum == targetSum) result.push_back(path);
            return;
        }
        for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
            sum += i; // 处理
            path.push_back(i); // 处理
            backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
            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.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

  • 输入:"23"
  • 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

总体思路

直观来看就是两层for循环进行输出,三层就是3个for循环
因此仍然使用回溯进行。
首先有三个问题:

  1. 数字和字母如何映射
  2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
  3. 输入1 * \ # 按键等等异常情况

映射

可以使用map或者定义一个二维数组,例如:string letterMap[10],来做映射,我这里定义一个二维数组,代码如下:

const string letterMap[10] = {
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};

回溯法来解决n个for循环的问题

对于回溯法还不了解的同学看这篇:关于回溯算法,你该了解这些!
例如:输入:"23",抽象为树形结构,如图所示:

图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。

回溯三部曲:
  • 确定回溯函数参数
    首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
    再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
    注意这个index可不是 77.组合216.组合总和III中的startIndex了。
    这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
    代码如下:
vector<string> result;
string s;
void backtracking(const string& digits, int index)
  • 确定终止条件
    例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。
    那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。
    然后收集结果,结束本层递归。
    代码如下:
if (index == digits.size()) {
    result.push_back(s);
    return;
}
  • 确定单层遍历逻辑
    首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集)。
    然后for循环来处理这个字符集,代码如下:
int digit = digits[index] - '0';        // 将index指向的数字转为int
string letters = letterMap[digit];      // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
    s.push_back(letters[i]);            // 处理
    backtracking(digits, index + 1);    // 递归,注意index+1,一下层要处理下一个数字了
    s.pop_back();                       // 回溯
}

注意这里for循环,可不像是在回溯算法:求组合问题!回溯算法:求组合总和!中从startIndex开始遍历的
因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而77. 组合216.组合总和III都是求同一个集合中的组合!
注意:输入1 * # 按键等等异常情况
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
但是要知道会有这些异常,如果是现场面试中,一定要考虑到!
代码:

// 版本一
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:
    vector<string> result;
    string s;
    void backtracking(const string& digits, int index) {
        if (index == digits.size()) {
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';        // 将index指向的数字转为int
        string letters = letterMap[digit];      // 取数字对应的字符集
        for (int i = 0; i < letters.size(); i++) {
            s.push_back(letters[i]);            // 处理
            backtracking(digits, index + 1);    // 递归,注意index+1,一下层要处理下一个数字了
            s.pop_back();                       // 回溯
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if (digits.size() == 0) {
            return result;
        }
        backtracking(digits, 0);
        return result;
    }
};

标签:index,int,sum,随想录,算法,result,回溯,字母组合,path
From: https://www.cnblogs.com/bailichangchuan/p/17155252.html

相关文章