二叉树:组合优化 组合总和III 电话号码的字母组合

组合优化

  • 前面组合问题中,我们通过回溯搜索法,解决了n个数中求k个数的组合问题。这道题中的回溯还可以进行剪枝优化,原因在于我们每次都从左向右取数,取过的数不再重复取,所以取到某个位置的数时,它和右边的数的总个数就达不到k个了,那么就没有必要继续搜索了。下图中,k为4,那么如果从元素2开始遍历,最多只能遍历到3个数,这样的遍历就是没有意义的遍历。
  • 在代码中从左到右取数体现在for循环,所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了
  • 优化过程:
    • 已经选择的元素个数:path.size();

    • 所需需要的元素个数为: k - path.size();

    • 列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())

    • 在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历(+1是因为包括起始位置,这是一个左闭的集合)

for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置


216.组合总和III

  • 思路:

    • 相比77.组合,本题多了一个k数之和为n的限制,k相当于树的深度,9(因为整个集合就是9个数)就是树的宽度
    • 回溯三部曲:
      • 递归函数参数:先定义全局变量path存储单一结果,res存储结果集,sum统计收集到的元素之和;参数需要限制的个数k,总和n,下一层for循环起始位置startIndex。
      • 终止条件:k已经限制树的深度,所以当path已经收集到k个元素时,判断总和sum和目标总和n,若相等,则收集path到res中。
      • 单层搜索过程:本题规定只使用[1,9],所以for循环固定i<=9;path每次收集元素,sum对应做累加统计总和,然后进行下一层递归,递归调用完后要记得回溯。
class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
    int sum = 0;
    void backtracking(int k, int n, int startIndex) {
        if(path.size() == k) {
            if(sum == n) res.push_back(path);
            return;
        }

        for(int i = startIndex; i <= 9; i++) {
            sum += i;
            path.push_back(i);
            backtracking(k, n, i + 1);//递归
            sum -= i;//回溯
            path.pop_back();//回溯
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        path.clear();
        res.clear();
        backtracking(k, n, 1);
        return res;
    }
};
  • 剪枝

    • 已选元素总和已经大于目标总和n,后续遍历已无意义,可以剪枝
    • 后续元素不足k个,可以剪枝,就是前面讲到的77.组合的优化
class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
    int sum = 0;
    void backtracking(int k, int n, int startIndex) {
        //剪枝
        //1.如果当前总和已经大于n,后续遍历已无意义
        if(sum > n) return;

        if(path.size() == k) {
            if(sum == n) res.push_back(path);
            return;
        }

        //2.for循环剪枝,元素不足k个时遍历已无意义
        for(int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
            sum += i;
            path.push_back(i);
            backtracking(k, n, i + 1);//递归
            sum -= i;//回溯
            path.pop_back();//回溯
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        path.clear();
        res.clear();
        backtracking(k, n, 1);
        return res;
    }
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)


17.电话号码的字母组合

  • 思路:
    • 首先要解决解决数字和字母的映射问题,可以使用map或一个二维数组string letterMap[10]来做映射。
    • 假设输入“23”,普通思路是使用两层for循环遍历,就可以输出所有情况,但是随着层数增加,这样的方法就行不通了。将输入“23”的搜索过程抽象为树形结构,如图:
    • 可以得出,树的深度就是输入数字的个度,而树的宽度就是由每个数字对应的字母个数决定的,叶子节点就是要收集的结果。
  • 回溯三部曲
    • 参数:先定义一个字符串s收集单一结果,字符串数组res做结果集。参数有题目要求需要输入的仅包含数字的字符串digits,以及一个整型index,这里的index与前面两题中的startIndex不同,前面的题目中求的是同一个集合中的组合,本题中求的是不同集合之间的组合,因此index用于记录遍历到digits中的第几个数字,也就是第几个集合,同时index也表示树的深度。
    • 终止条件:如果digits中的数字遍历完了(index = digits.size()) ,说明遍历结束,然后收集结果,结束本层递归。
    • 单层遍历逻辑首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集),然后for循环来处理这个字符集。
    • 补充1:考虑输入1 * #按键等等异常情况,但题目的测试数据中应该没有异常情况的数据,本题可略过。
    • 补充2:本题没有剪枝,因为要收集的就是所有组合
  • 时间复杂度: O(3^m * 4^n),其中 m 是对应四个字母的数字个数,n 是对应三个字母的数字个数
  • 空间复杂度: O(3^m * 4^n)
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
    };

    vector<string> res;
    string s;

    void backtracking(string digits, int index) {
        if(index == digits.size()) {
            res.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();//回溯
        }
    }

public:
    vector<string> letterCombinations(string digits) {
        res.clear();
        s.clear();
        if(digits == "") return res;
        backtracking(digits, 0);
        return res;
    }
};

总结

注意求一个集合中的组合问题,和不同集合之间的组合问题的区别

参考链接

代码随想录:组合优化 组合总和III  电话号码的字母组合

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值