day25C++ 回溯算法 组合总和Ⅲ 电话号码的字母组合

题目1:216组合的总和Ⅲ

题目链接:组合总和Ⅲ

对题目的理解

使用数字1~9,找出k个数,这k个数的和为n,每个数字最多使用一次

k控制树的深度

回溯法

回溯三部曲

i)确定递归函数参数

全局变量一维数组path来存放符合条件的结果,二维数组result来存放结果集。

  • targetSum(int)目标和,也就是题目中的n。
  • k(int)就是题目中要求k个数的集合。
  • sum(int)为已经收集的元素的总和,也就是path里元素的总和。
  • startIndex(int)为下一层for循环搜索的起始位置。

ii)确定终止条件

①k其实就已经限制树的深度,因为就取k个元素,树再往下深了没有意义。所以如果path.size() 和 k相等了,就终止。

②如果此时path里收集到的元素和(sum) 和targetSum(就是题目描述的n)相同了,就用result收集当前的结果。

iii)单层搜索过程

处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。

!!!!别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减

代码

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(int targetsum,int sum,int k,int startindex){
        //终止条件
        if(path.size()==k){
            if(targetsum==sum){
                result.push_back(path);
                return;
            }
        }
        //单层递归逻辑
        for(int i=startindex;i<=9;i++){
            sum+=i;
            path.push_back(i);
            backtracking(targetsum,sum,k,i+1);//递归
            sum-=i;//回溯
            path.pop_back();//回溯
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        path.clear();
        result.clear();
        int sum=0;
        backtracking(n,sum,k,1);
        return result;
    }
};

剪枝

①已选元素总和如果已经大于n了,那么往后遍历就没有意义了,直接剪掉,剪枝可以放在递归函数开始的地方

for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。

代码

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(int targetsum,int sum,int k,int startindex){
        if(sum>targetsum) return;
        //终止条件
        if(path.size()==k){
            if(targetsum==sum){
                result.push_back(path);
                return;
            }
        }
        //单层递归逻辑
        for(int i=startindex;i<=9-(k-path.size())+1;i++){
            sum+=i;
            path.push_back(i);
            backtracking(targetsum,sum,k,i+1);//递归
            sum-=i;//回溯
            path.pop_back();//回溯
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        path.clear();
        result.clear();
        int sum=0;
        backtracking(n,sum,k,1);
        return result;
    }
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

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

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

对题目的理解

仅含数字2-9的字符串,返回所有能表示的字母组合,注意是根据数字返回字母!!!

要解决如下三个问题

1)数字和字母如何映射

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

2)两个字母就两个for循环,三个字符我就三个for循环,以此类推,.......想到使用回溯法

回溯法

回溯三部曲

输入数字的个数就是树的深度,树的宽度就是数字所代表的字母的长度

i)确定回溯函数参数

全局变量:一个字符串s来收集叶子节点的单个结果,用一个字符串数组result所有结果保存

再来看参数,有题目中给的string digits,然后还要有一个参数就是int型的index。index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。

ii)确定终止条件

叶子节点就是要收集的结果集,那么终止条件就是如果index 等于输入的数字个数(digits.size)

iii)确定单层遍历逻辑

首先要取index指向的数字,接着找到对应的字符集(手机键盘的字符集),然后for循环来处理这个字符集,每一个数字代表的是不同集合,也就是求不同集合之间的组合

3)输入1 * #按键等等异常情况知道会有这些异常,如果是现场面试中,一定要考虑到!

代码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:
    string s;//存放单个结果
    vector<string> result;//存放所有结果
    void backtracking(int index,const string& digits){
        //index 当前遍历到哪个数字了
        //终止条件
        if(index==digits.size()){
            result.push_back(s);
            return;
        }
        //单层遍历
        //找到数字
        int dig = digits[index]-'0';
        //找到数字对应的字符串
        string letter = letterMap[dig];
        for(int i=0;i<letter.size();i++){
            s.push_back(letter[i]);//处理
            backtracking(index+1, digits);//递归
            s.pop_back();//回溯
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if(digits.size()==0) return result;
        backtracking(0, digits);
        return result;
    }
};

代码1和代码2的不同之处,就在于终止条件的if判断处,代码1判断index遍历到第几个数字和digits的大小进行比较,当等于时,说明遍历完成,可以收获结果了;代码2是比较单个结果内的元素个数和digits中的元素个数,如果相等,则说明遍历完成,可以收获结果了

代码2:

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(int index,const string& digits){
        //index 当前遍历到哪个数字了
        //终止条件
        if(s.size()==digits.size()){
            result.push_back(s);
            return;
        }
        //单层遍历
        //找到数字
        int dig = digits[index]-'0';
        //找到数字对应的字符串
        string letter = letterMap[dig];
        for(int i=0;i<letter.size();i++){
            s.push_back(letter[i]);//处理
            backtracking(index+1, digits);//递归
            s.pop_back();//回溯
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if(digits.size()==0) return result;
        backtracking(0, digits);
        return result;
    }
};
  • 时间复杂度: O(3^m * 4^n),其中 m 是对应四个字母的数字个数,n 是对应三个字母的数字个数
  • 空间复杂度: O(3^m * 4^n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值