216.组合总和III17.电话号码的字母组合
216.组合总和III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]
思路:回溯
回溯法适用于从n个数中,找出符合条件的集合,此题满足回溯条件
此题要求只使用数字1到9,那么结果集一定在【1,2,3,4,5,6,7,8,9】中,将其作为根节点递归遍历那么1到9就是根节点的9个子树,用for循环遍历
每一次遍历确保递归传递的结果集大于i,这样不会有重复参数存在
回溯法三要素
方法参数和返回值:参数 int k,int n,int startindex 返回值void
中止条件:k表示树的深度,递归达到深度k,结果集元素的数量才满足提议,所以当path.size == k,判断结果集元素综合等于n,如果等于,则加入result
核心逻辑对【1,2,3,4,5,6,7,8,9】for循环遍历,遍历起始值由startIndex决定,startIndex初始等于1
递归时每次给startIndex+1
回溯取出path最后一个元素
代码如下
// 时间复杂度:分析回溯问题的时间复杂度,有一个通用公式:路径长度×搜索树的叶子数。对于本题,它等于 O(k⋅C(9,k))
// 空间复杂度:O(n+k)=O(n),即递归使用栈空间的空间代价和临时数组
List<Integer> path = new ArrayList<>();
List<List<Integer>> result = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum3(int k, int n) {
backTracking(k, n, 1);
return result;
}
public void backTracking(int k, int n, int startIndex) {
if(sum > n)
return;
if (path.size() == k) {// 终止条件
int sum = 0;
for (int i = 0; i < path.size(); i++) {
sum = sum + path.get(i);
}
if (sum == n) {
result.add(new ArrayList<>(path));
}
return;
}
for (int i = startIndex; i <= 9 - k + path.size() + 1; i++) {
path.add(i);
backTracking(k, n, i + 1);
path.remove(path.size() - 1);
}
}
剪枝 优化
我只想到了在添加限制for循环的范围,i <= 9 - (k - path.size()) + 1
可以从sum入手,如果sum > n,由于数字都是整数,遍历的结果只会越来越大,没必要遍历下去了
代码如下
public void backTracking(int k, int n, int startIndex) {
if(sum > n)
return;
17.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
- 输入:“23”
- 输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
思路:回溯
将数字入参转化字符串。例如“234”转化为“abcdefghi"
abc不能相互组合,def不能相互组合,ghi不能相互组合
但abc取出其中一个,可以和def取出其中一个,ghi取出其中一个互相组合
首先定义字符串数组,来完成数字和字符串的映射
String[] array = {“”, “”, “abc”, “def”, “ghi”, “jkl”, “mno”, “pqrs”, “tuv”, “wxyz”};
定义index = 0; index跟前面startIndex作用不一致,它用来表示第几个数字,从【字符串digits取出数字】,也可作为中止条件回溯法三要素
方法入参和返回值 入参:String digits,int index 返回值 void
中止条件 index == 数组字符串的长度
核心逻辑
1.for循环对当前数字映射字符串遍历,比如2对应"abc"
2.递归 :stringbuilder添加字符,比如"a".将index+1,传入递归方法中,便于找到下一个数字对应字符串
3.回溯:stringbuilder去除字符,比如"ad"变为"a"
代码如下
// 时间复杂度:分析回溯问题的时间复杂度,有一个通用公式:路径长度×搜索树的叶子数。对于本题2,3,4,5,6,8数字对应三个字符,7,9对应四个字符。假设m为三个字符数字出现次数,n为四个字符数字出现次数,那么不同的字母组合一共有 O(3^m * 4^n) 而路径长度固定在0-9之间,可当作常数,所以时间复杂度为O(3^m * 4^n)
// 空间复杂度:O(3^m * 4^n), 3^m * 4^n是搜索树结点的数量。递归的深度最多为8层,每递归一层,结果集的字母数量+1,那么结果集最多有8个字符。很明显O(3^m * 4^n) 大于O(递归栈深度),所以空间复杂度为O(3^m * 4^n)
List<String> result = new ArrayList<>();
StringBuilder stringBuilder = new StringBuilder();
String[] array = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if(digits == null || digits.length() == 0)
return result;
backTracking(digits, 0);
return result;
}
public void backTracking(String digits, int index) {
if (index == digits.length()) {//中止条件:遍历过数字数量,等于digits的数字数量
result.add(stringBuilder.toString());
return;
}
String str = array[digits.charAt(index) - '0'];// 当前数字对应的字符串
for (int i = 0; i < 3; i++) {// digits.charAt(index) 数字,对应字符串遍历
stringBuilder.append(str.charAt(i));
backTracking(digits, index + 1);
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
}
}
问题
忘记处理边缘情况了.如果不对digits边缘情况判断
会导致出错
解决方式如下
public List<String> letterCombinations(String digits) {
if(digits == null || digits.length() == 0)
return result;
backTracking(digits, 0);
return result;
}