Day28~216.组合总和III、17.电话号码的字母组合

121 阅读4分钟

摘要

本文主要介绍了LeetCode回溯算法的几个题目,包括216.组合总和III、17.电话号码的字母组合。

1、216.组合总和III

1.1 思路

  1. 递归函数的参数: 在递归函数中,我们需要传递以下参数:

    • k:表示还需要选择几个数字。
    • n:表示目标和。
    • start:表示当前考虑的数字的起始位置。
    • path:表示当前的组合。
  2. 满足结束条件: 在递归函数的开头,首先判断是否满足结束条件。结束条件有两个:

    • k 等于 0 且 n 等于 0 时,表示已经选择了 k 个数字且和为 n,将当前的组合加入结果集。
    • k 不等于 0 且 n 小于 0 时,表示无法找到满足条件的组合,直接返回。
  3. 进行选择和递归: 在递归函数的主体部分,我们需要考虑两种情况:

    • 选择当前数字 i,将其添加到当前组合 path 中,然后递归调用函数,传递参数 k - 1n - ii + 1
    • 不选择当前数字 i,直接递归调用函数,传递参数 kn
  4. 撤销选择: 在每一次递归返回后,需要撤销选择,即将当前数字从组合 path 中移除,以便考虑下一个数字的选择。

  5. 回溯搜索: 通过不断地进行选择和递归,回溯算法会搜索所有可能的组合。

1、可以剪枝优化吗?

第一步剪枝: 如果在递归的某一层中,发现 n - i 小于 0,这意味着在当前状态下,无论后面选择哪些数字,都不可能满足和为 n 的条件。因此,可以直接返回,不再继续向下递归,从而减少不必要的计算。

第二步剪枝: i 的取值范围是 [start, 9 - k + 1],确实是为了确保每次选择的数字不会超出范围,以满足问题的要求。举例来说,当 k = 3, n = 7 时,i 可以取 7,但不能取 8,因为数字范围只允许 1 到 9。

2、216.组合总和III与77.组合剪枝的区别?

在第 216 题中,你提到 i 的取值范围是 [start, 9 - k + 1],因为 k 在递归中会减小,所以要考虑可选数字的范围应该是 [start, 9 - k + 1]

在第 77 题中,你提到 i 的取值范围是 [start, (n - k + 1) + linked.size()],因为 k 在递归中不变,所以要考虑可选数字的范围是 [start, (n - k + 1) + linked.size()]

1.2 代码

    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        doCombinationSum3(list, path, k, n, 1);
        return list;
    }
    
    public void doCombinationSum3(List<List<Integer>> list, LinkedList<Integer> path, int k, int n, 
            int start) {
        if(k == 0) {
            if(n == 0) {
                list.add(new ArrayList<>(path));
            }
            return;
        }
        
        for(int i=start; i<=9-k+1; i++) {
            if(n - i < 0) {
                continue;
            }
            path.add(i);
​
            doCombinationSum3(list, path, k-1, n-i, i+1);
​
            path.removeLast();
        }
    }

2、17.电话号码的字母组合

2.1 思路

17. 电话号码的字母组合

  • 遍历输入的电话号码数字串,对每个数字找到其对应的字母集合,然后递归地生成所有可能的字母组合
  • 递归的终止条件是当前组合的长度等于输入数字串的长度,此时将组合加入结果集
  • 在递归的过程中,需要不断地选择一个字母,加入组合,然后继续递归下一层,最后撤销选择以尝试下一个字母

1、与其他组合问题的区别?

特定输入和字母映射: 在电话号码的字母组合问题中,输入是一个包含数字的字符串,例如 "23",而不是一组数字数组。此外,电话号码的字母映射是固定的,每个数字都对应了一组字母,例如 '2' 对应 "abc",'3' 对应 "def",以此类推。其他组合问题可能涉及更一般化的输入和自定义的字母映射。

2.2 代码

    public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<>();
        if (digits == null || digits.length() == 0) {
            return list;
        }
        
        // 定义数字到字母的映射
        String[] arr = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        // 用于暂存每一层的字母组合
        StringBuilder builder = new StringBuilder();
​
        doLetterCombinations(list, arr, digits, 0, builder);
        return list;
    }
​
    public void doLetterCombinations(List<String> list, String[] arr, String digits, int start, StringBuilder builder) {
        // 如果当前组合的长度等于输入数字串的长度,将其加入结果集
        if(start == digits.length()) {
            list.add(builder.toString());
            return;
        }
​
        // 获取当前数字对应的字母集合
        String letters = arr[digits.charAt(start)-'0'];
        for(int i=0; i<letters.length(); i++) {
            // 将该字母加入组合
            builder.append(letters.charAt(i));
​
            // 继续递归下一层
            doLetterCombinations(list, arr, digits, start+1, builder);
​
            // 撤销选择,以便尝试下一个字母
            builder.deleteCharAt(builder.length()-1);
        }
    }

参考资料

代码随想录-216.组合总和III

代码随想录-17.电话号码的字母组合