Day21 力扣回溯 : 216.组合总和III | 17.电话号码的字母组合
216.组合总和III
如果把 组合问题理解了,本题就容易一些了。
题目链接/文章讲解:https://programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CIII.html
视频讲解:https://www.bilibili.com/video/BV1wg411873x
第一印象:
感觉是可以套模板,但我对剪枝的逻辑一点思路没有,直接看题解学习一下模板的使用。
看完题解的思路:
我觉得思路不难,还是挺模板题的,下次做题的时候得学着自己画那个树形图了。
代码随想录里的图:
这道题不像第一个组合,只需要path添添删删,因为只是需要路径长度而已。而这道题不仅需要路径长度,还需要这些数字的总和sum满足target。
所以for循环做操作的时候要对path和sum一起操作,并回溯。
返回值和参数:
回溯大部分返回值void,参数除了题里的n、k和startIndex外,因为要记录path内的总和,所以也要有个sum。这样在递归给子节点的时候就有一种:现在已经有这么多了。的感觉了
终止条件:
k限制树的高度,因为只需要k个数字,再往下就没有意义了。所以当path里的数字有k个了,就可以终止了。这道题还需要判断sum是否和target相等,满足要求的才需要加入result.
单层递归逻辑:
这道题不像第一个组合,只需要path添添删删,因为只是需要路径长度而已。而这道题不仅需要路径长度,还需要这些数字的总和sum满足target。
所以for循环做操作的时候要对path和sum一起操作,并回溯。
现在去学习一下tmd剪枝吧。
这道题的剪枝其实是我不敢去想哈哈哈,看了一下觉得简单的。
就是如果sum已经超过target了,就没必要继续了。
所以在递归函数的开头可以写
if (sum > target) return;
其次是for循环里的因为k(个数)限制的剪枝,和上一道题是一样的:
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
实现中的困难:
第一次做的时候把for循环的终止条件想当然弄成path.size()了
感悟:
剪枝有点自由发挥,但是对于for循环里的因为k(个数)限制的剪枝有了更深的理解,虽然这种 +1 真的恶心人。
代码:
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(k, n, 0, 1);
return result;
}
private void backtracking(int k, int target, int sum, int startIndex) {
if (sum > target) return;
if (path.size() == k) {
if (sum == target) {
result.add(new ArrayList<>(path));
}
return;
}
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
path.add(i);
sum += i;
backtracking(k, target, sum, i + 1);
path.removeLast();
sum -= i;
}
}
}
17.电话号码的字母组合
本题大家刚开始做会有点难度,先自己思考20min,没思路就直接看题解。
题目链接/文章讲解:https://programmercarl.com/0017.%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%E7%9A%84%E5%AD%97%E6%AF%8D%E7%BB%84%E5%90%88.html
视频讲解:https://www.bilibili.com/video/BV1yV4y1V7Ug
第一印象:
让我先思考,ok,我先回家做口饭然后就思考。现在看的话觉得两层回溯??
等会到家再看哈
吃饱了,我的猪肉炖粉条做的还行啊,真是天才,下次少放点油就行。
这道题给出的是字符串,不再是连续的数字,需要把字符串拆开先。
观察例子,比如数字23,那么答案的组合是 一个字母来自2,一个字母来自3,组合在一起的答案,不包括只来自2的,只来自3的,还有22,33的情况。
我大概知道怎么做出选数字的,但是怎么选字母呢??我有点实现不出来,我觉得我的代码实现能力比算法思路要弱。
直接看题解吧
看完题解的思路:
明白了,画的图应该是
这里要注意,之前的题目是在一个集合里面搜索,startIndex是下一次搜索的位置。而这道题是两个集合,不需要startIndex。
返回值和参数:
返回值还是经典的void,参数肯定有题目中的string digits,还需要一个int index 来记录遍历到字符串里的哪个位置了。
终止条件:
终止条件就是字符串遍历完了。
if (index == digits.size()) {
result.push_back(s);
return;
}
单层递归逻辑:
这里我觉得是这道题最难的,我没能在看完题解之后自己做出来,我照着题解做一遍试试。
我觉得我对字符串的操作掌握的不好。
首先到map中 找到当前的数字 所对应的字母们(字符串)。
int digit = digits[index] - '0'; // 将index指向的数字转为int
string letters = letterMap[digit]; // 取数字对应的字符集
然后在这些字母的集合里进行for循环的操作
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
这样去看这道题就清晰一些了,操作的集合是每个数字所对应的字母们(字符串)。 是很多个集合的操作,而不是像之前的题只在一个集合里。
只在一个集合里需要startIndex来标识下一次搜索的位置。而这样很多个集合,就是把另一个集合(比如数字2对应的“abc”)直接拿过来做了。但是这个集合拿过来的过程,是从数字串那一一对应过来的。
数字串是一个一个数字遍历的(比如23),就需要index来标明遍历到哪个了,这个过程在树形图里很明显。
其实一个数字对应的字母的数量是这个树的宽度
有多少个数字是这个树的高度。
写到这我觉得我捋清了这道题的思路,希望下次做能直接做出来。就是字符串的操作我有点陌生。
//每次迭代获取一个字符串,所以会设计大量的字符串拼接,所以这里选择更为高效的 StringBuild
StringBuilder temp = new StringBuilder();
他用stringBuilder去收集一个个结果,最后放在result里。
这道题没剪枝哈哈哈哈
实现中的困难:
我没能自己做出来,遇到字符串的操作就懵了,而且对不同集合的for循环操作也很懵
感悟:
不同集合的回溯,首先找到不同集合(树形图很适合理解)
再去操作那个for循环
代码:
class Solution {
List<String> list = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return list;
}
String[] numberList = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backtracking(digits, numberList, 0);
return list;
}
//每次迭代获取一个字符串,所以会设计大量的字符串拼接,所以这里选择更为高效的 StringBuild
StringBuilder temp = new StringBuilder();
private void backtracking(String digits, String[] numberList, int index) {
if (index == digits.length()) {
list.add(temp.toString());
return;
}
String curStr = numberList[digits.charAt(index) - '0'];
for (int i = 0; i < curStr.length(); i++) {
temp.append(curStr.charAt(i));
backtracking(digits, numberList, index + 1);
temp.deleteCharAt(temp.length() - 1);
}
}
}