77. 组合
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> value = new ArrayList<>();
backtracking(n, k, 1);
return result;
}
private void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.add(new ArrayList<>(path));
return;
}
// if n = k = 4. 那么,只有1,2,3,4满足需求,后面从2开始的都不满足,说明for循环中n的值可以根据需要来进行缩减
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
path.add(i);
backtracking(n, k, i + 1);
path.removeLast();
}
}
}
回溯,回溯算法使用同一个模板,关键是做的题多,自然就写出来了,如果刚学回溯不要担忧很困难。
写个一周,自然就懂怎么写回溯了。
剪枝是写完回溯再去分析的问题,不用在写的过程反复去推敲,反而啥都写不好。先写出来,在优化。
剪枝:
该题目的剪枝操作,当长度小于了约定的个数,就无法再找到新的排列组合了。比如,n=3,k=4,那么一个都没有
216. 组合总和 III
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
// 从[1,9]之间选取k个数,相加得到n
backtracking(k, n, 1);
return result;
}
private void backtracking(int k, int n, int startIndex) {
if (path.size() == k) {
if (n == 0) {
result.add(new LinkedList<>(path));
}
return;
}
// 从1开始
for (int i = startIndex; i <= 9; i++) {
path.add(i);
backtracking(k, n - i, i + 1);
path.removeLast();
}
}
}
未剪枝代码如上所示,那么剪枝呢?
因为是在[1,9]之间选取数字,那么每一个数字是不相同的。
如果k=2,n=4,假设n1 = 2, n2 = 3,发现sum = n1 + n2 >= n, 那么后面的数还有遍历的必要么?
private boolean backtracking(int k, int n, int startIndex) {
if (path.size() == k) {
if (n == 0) {
result.add(new LinkedList<>(path));
}
return n <= 0;
}
// 从1开始
for (int i = startIndex; i <= 9; i++) {
path.add(i);
boolean backtracking = backtracking(k, n - i, i + 1);
path.removeLast(); // 位置放在上面来了,因为要把当前层级放入的先移除再判断
if (backtracking) {
return !backtracking; // 上一级还是需要正常遍历的
}
// path.removeLast();
}
return false;
}
再思考一下,如果一个元素,进去就比期望的元素大,那么还有必要向后面查找么?
比如想查找n=4,k=2,结果n1 = 4.
private boolean backtracking(int k, int n, int count, int startIndex) {
if (startIndex > n) { // 不能设置等于,如果k=1,就挂了
return true;
}
if (path.size() == k) {
if (count == 0) {
result.add(new LinkedList<>(path));
}
return count <= 0;
}
// 从1开始
for (int i = startIndex; i <= 9; i++) {
path.add(i);
boolean backtracking = backtracking(k, n, count - i, i + 1);
path.removeLast(); // 位置放在上面来了,因为要把当前层级放入的先移除再判断
if (backtracking) {
return !backtracking; // 上一级还是需要正常遍历的
}
// path.removeLast();
}
return false;
}
但在leecode上,发现这个算法的时间居然比上一个慢,只能说明测试用例和复杂度并不强。
毕竟这只是一个10以内的加法,最大值为55.
17. 电话号码的字母组合
private String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
private List<String> result = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) {
return result;
}
backtracking(digits, 0, new StringBuilder());
return result;
}
private void backtracking(String digits, int startIndex, StringBuilder path) {
if (path.length() == digits.length()) {
result.add(path.toString());
return;
}
String str = numString[digits.charAt(startIndex) - '0'];
for (int i = 0; i < str.length(); i++) {
path.append(str.charAt(i));
backtracking(digits, startIndex + 1, path);
path.deleteCharAt(path.length() - 1);
}
}
问题并不难,有一个难点在字符数字转为数字。
如果使用int去接收,它不会转为char类型对应的数字,而是ascll编码表对应的数字