代码随想录算法训练营第25天 | 回溯算法part02 216.组合总和III 17.电话号码的字母组合
题目一 组合总和3
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
关键:本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。
其余思路和上一题相同。
本题k相当于树的深度,9(因为整个集合就是9个数)就是树的宽度。
- target目标和,也就是题目中的n。
- k就是题目中要求k个数的集合。
- sum为已经收集的元素的总和,也就是path里元素的总和。
- startIndex为下一层for循环搜索的起始位置。注意startindex所处的集合是1~9数字的集合,因此下标从1开始,到9结束。
public List<List<Integer>> ans = new ArrayList<>();
public LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n)
{
recursion(n, k, 0, 1);//sum = 0
return ans;
}
//k为深度,9为长度
public void recursion(int target, int k, int sum, int startindex)//参数不着急定
{
//end
if(path.size() == k)//限制深度
{
if(sum == target)
{
ans.add(new ArrayList<>(path) );
return;
}
}
//every
for(int i = startindex; i <= 9; i++)//[1,2,3,4,5,6,7,8,9]
{
sum += i;
path.add(i);//add this
recursion(target,k, sum,i+1);
sum -= i;
path.removeLast();//delete this
}
}
sum为统计已有数字加起来的总和,会随着每次迭代更新,因此加入形参中。其实这里sum这个参数也可以省略,
每次target减去选取的元素数值,然后判断如果target为0了,说明收集到符合条件的结果了。
target为总和应该达到的目标,可使用全局变量,不用每次都传入。
同理,剪枝操作也很容易想到。本题剪枝的因素有两个。
已选元素总和如果已经大于n(图中数值为4)了,那么往后遍历就没有意义了,直接剪掉。
同样,for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backTracking(n, k, 1, 0);
return result;
}
private void backTracking(int targetSum, int k, int startIndex, int sum) {
// 减枝
if(sum > targetSum)
{
return;
}
if (path.size() == k)
{
if (sum == targetSum)
result.add(new ArrayList<>(path));
return;
}
// 减枝 9 - (k - path.size()) + 1
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++)
{
path.add(i);
sum += i;
backTracking(targetSum, k, i + 1, sum);
//回溯
path.removeLast();
//回溯
sum -= i;
}
}
题目二 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与九键输入法按键相同)。注意 1 不对应任何字母。
理解本题后,要解决如下三个问题:
- 数字和字母如何映射
- 两个字母就两个for循环,三个字符就三个for循环,以此类推,然后发现代码根本写不出来
- 输入1 * #按键等等异常情况
与之前题目不同的是,本题数字和字母应当使用map或者二维数组进行映射。
横向为输入数字的顺序,纵向为选择字符的顺序。
下面用一个字符串数组allletter[]保存所有映射,以数组下标为数字,存储值为对应的字符串。
从这个数组中取的时候注意考虑字符串和它对应的数字的转换,根据不同语言的特性选择相应的转换函数。
注意考虑输入为空的情况,应该返回一个空列表[] 而不是[“”] .
index是用于遍历给定字符串digits的,因此下标从0开始,调用函数传参开始也是0.
如果index与digit长度相等,则证明遍历完毕,将一个字符串结果加入答案中。
public List<String> ans = new ArrayList<>();
public StringBuilder path = new StringBuilder();
private String[] allletter = {//0, 1, 2, 3, 4, 5, 6, 7, 8, 9
"" ,"" ,"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
};
public List<String> letterCombinations(String digits)
{
if(digits == null || digits.length() == 0)
return ans;
recursion(digits, allletter, 0);
return ans;
}
public void recursion(String digits, String[] allletter, int index)
{
//end
if(index == digits.length() )
{
ans.add(path.toString() );
return;
}
int digit = digits.charAt(index) - '0';//letter to num
String letter = allletter[digit];
//every
for(int i=0; i<letter.length(); i++)
{
path.append( letter.charAt(i) );//加入字符
recursion(digits,allletter, index+1);
path.deleteCharAt(path.length() - 1);//回溯
}
}
注意这里for循环,可不像是在 回溯算法:求组合问题 和 回溯算法:求组合总和 中,从startIndex开始遍历的。因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合。
- 时间复杂度: O( 3 m ∗ 4 n 3^m * 4^n 3m∗4n),其中 m 是对应四个字母的数字个数,n 是对应三个字母的数字个数
- 空间复杂度: O( 3 m ∗ 4 n 3^m * 4^n 3m∗4n)