今日任务
题目 :216. 组合总和 III
思路
k决定了树的深度
判断dfs退出的条件:数组之和为 n
回溯三部曲
-
确定递归函数参数
这里我定义path 和 result为全局变量。
至于为什么取名为path?从上面树形结构中,可以看出,结果其实就是一条根节点到叶子节点的路径。
接下来还需要如下参数:
-
targetSum(int)目标和,也就是题目中的n。
-
k(int)就是题目中要求k个数的集合。
-
sum(int)为已经收集的元素的总和,也就是path里元素的总和。
-
startIndex(int)为下一层for循环搜索的起始位置。
所以代码如下:
public void dfs (int k, int n, int startIdx, int sum)
其实这里sum这个参数也可以省略,每次targetSum减去选取的元素数值,然后判断如果targetSum为0了,说明收集到符合条件的结果了,我这里为了直观便于理解,还是加一个sum参数。
还要强调一下,回溯法中递归函数参数很难一次性确定下来,一般先写逻辑,需要啥参数了,填什么参数。
-
确定终止条件
所以如果path.size() 和 k相等了,就终止。
如果此时path里收集到的元素和(sum) 和targetSum(就是题目描述的n)相同了,就用result收集当前的结果。
所以 终止代码
if (path.size() == k) {
ans.add(new ArrayList<>(path));
return;
}
-
单层搜索过程
处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。
for (int i = startIdx; i <= 9; i++ ) {
path.add(i);
sum += i;
dfs(k, n, i + 1, sum);
sum -= i;
path.removeLast();
}
别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!
剪枝
if (sum > n) return;
if (path.size() > k) return;
if (sum == n && path.size() == k) {
ans.add(new ArrayList<>(path));
return;
}
题解
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
dfs(k, n, 1, 0);
return ans;
}
public void dfs (int k, int n, int startIdx, int sum) {
if (sum > n) return;
if (path.size() > k) return;
if (sum == n && path.size() == k) {
ans.add(new ArrayList<>(path));
return;
}
for (int i = startIdx; i <= 9; i++ ) {
path.add(i);
sum += i;
dfs(k, n, i + 1, sum);
sum -= i;
path.removeLast();
}
}
}
题目 :17. 电话号码的字母组合
思路
组合型枚举
首先对2-9数字对应的字母进行存储
依据输入的数字个数决定树的深度
回溯三部曲:
-
确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
public void dfs(String digits, String[] numString, int num)
-
确定终止条件
例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。
那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。
然后收集结果,结束本层递归。
if (num == digits.length()) {
list.add(temp.toString());
return;
}
-
确定单层遍历逻辑
首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集)。
然后for循环来处理这个字符集,代码如下:
String str = numString[digits.charAt(num) - '0'];
for (int i = 0; i < str.length(); i++ ) {
temp.append(str.charAt(i));
dfs(digits, numString, num + 1);
temp.deleteCharAt(temp.length() - 1);
}
注意这里for循环,都是求同一个集合中的组合!
注意:输入1 * #按键等等异常情况
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
但是要知道会有这些异常,如果是现场面试中,一定要考虑到!
题解
class Solution {
// 定义一个全局的List来存储所有可能的字母组合
List<String> list = new ArrayList<>();
public List<String> letterCombinations(String digits) {
// 如果输入的字符串为空,直接返回空列表
if (digits == null || digits.length() == 0) {
return list;
}
// 对应数字到字母的映射,注意0和1不对应任何字母
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
// 开始深度优先搜索
dfs(digits, numString, 0);
return list;
}
// 用于构建当前遍历到的字符串
StringBuilder temp = new StringBuilder();
// 深度优先搜索函数
public void dfs(String digits, String[] numString, int num) {
// 如果当前的长度和digits长度相等,说明找到了一个组合
if (num == digits.length()) {
list.add(temp.toString());
return;
}
// 获取当前数字对应的所有可能字母
String str = numString[digits.charAt(num) - '0'];
// 遍历这些字母
for (int i = 0; i < str.length(); i++ ) {
// 将当前字母加到构建中的字符串
temp.append(str.charAt(i));
// 继续深度优先搜索下一个数字
dfs(digits, numString, num + 1);
// 回溯,移除最后一个字符,尝试下一个可能的字母
temp.deleteCharAt(temp.length() - 1);
}
}
}