216.组合总和III
回溯三部曲
- 确定递归函数参数
- targetSum(int)目标和,也就是题目中的n。
- k(int)就是题目中要求k个数的集合。
- sum(int)为已经收集的元素的总和,也就是path里元素的总和。
- startIndex(int)为下一层for循环搜索的起始位置。
- 确定终止条件
-
k其实就已经限制树的深度,因为就取k个元素,树再往下深了没有意义。
所以如果path.size() 和 k相等了,就终止。
如果此时path里收集到的元素和(sum) 和targetSum(就是题目描述的n)相同了,就用result收集当前的结果。
- 单层搜索过程
-
处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。
在纸上模拟了一下,例如到path:[1,2,3,4]的时候开始回溯
class Solution {
List<List<Integer>> res=new ArrayList<>();
LinkedList<Integer> path=new LinkedList<>();
// n:目标和,也就是题目中的n。
// k:题目中要求k个数的集合。
// sum:已经收集的元素的总和,也就是path里元素的总和。
// startIndex:下一层for循环搜索的起始位置
public List<List<Integer>> combinationSum3(int k, int n) {
// 只使用数字1到9
backtracking(k,n,0,1);
return res;
}
void backtracking(int k, int n,int sum,int startIndex){
//终止条件
if(path.size()==k&&sum==n){
res.add(new ArrayList<>(path));
//如果这里是res.add(path),那么path其实都是[]
return;
}
//剪枝
if(sum>n){//这段没有回导致栈溢出
return;
}
//处理逻辑,横向搜索
for(int i=startIndex;i<=9-(k-path.size())+1;i++){//剪枝条件理解重点
sum+=i;
path.add(i);
backtracking(k,n,sum,i+1);//这一步相当于对树的纵向遍历,
sum-=i;//回溯
path.removeLast();//移除最新进来的元素
}
}
}
for(int i=startIndex;i<=9-(k-path.size())+1;i++){//剪枝条件理解重点
遍历的范围是可以剪枝优化的,怎么优化呢?
来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了(不足k个)。 在第二层for循环,从元素3开始的遍历都没有意义了(不足k个)。如图
图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
接下来看一下优化过程如下:
-
已经选择的元素个数:path.size();
-
还需要的元素个数为: k - path.size();
-
在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
从2开始搜索都是合理的,可以是组合[2, 3, 4]。
17. 电话号码的字母组合
没有思路啊丢
首先要遍历digits的每一个数字,一个数字对应一个字符串str,如digits=“23”,可以得到str1=“abc”,str2=“def”,我们求str1和str2的所有组合
树型结构:digits的长度为树的深度,每一层的宽度取决于对应str的字符个数
每次调用 backTracking就相当于遍历digits的下一个数字
class Solution {
//设置全局列表存储最后的结果
List<String> res = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if(digits.length()==0){
return res;
}
//组合问题
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backTracking(digits,numString,0);
return res;
}
StringBuilder sb=new StringBuilder();
public void backTracking(String digits,String[] numString,int num){
//遍历digits全部数字对应的字符串一次 记录一次得到的字符串
if(num==digits.length()){
res.add(sb.toString());
return;
}
//str表示当前num对应的字符串
String str=numString[digits.charAt(num)-'0'];
for(int i=0;i<str.length();i++){
sb.append(str.charAt(i));
backTracking(digits,numString,num+1);
//回溯,剔除末尾的继续尝试
sb.deleteCharAt(sb.length()-1);
}
}
}