题目
77. 组合
题解:
回溯的三部曲:做选择 -> 递归 -> 撤销选择
时间复杂度:叶子个数( C ( n , k ) C(n,k) C(n,k)) × 路径长度( k k k),即 O ( k ∗ C ( n , k ) ) O(k*C(n,k)) O(k∗C(n,k))
组合型回溯代码。
private void dfs(int i, int k, List<Integer> path, List<List<Integer>> ans) {
// 1. 计算还需要选几个数
int leftNums = k - path.size();
// 2. 终止条件
if(path.size() == k) {
// 重要:必须 new 一个新的 list 存进去。
// 如果直接存 path,因为 path 是引用,后续的修改会把这里存的变成空的。
ans.add(new ArrayList<>(path));
return;
}
// 3. 循环选择(核心逻辑)
for(int j = i; j >= leftNums; j--) {
path.add(j); // 【做选择】:把数字 j 放入篮子
dfs(j-1, k, path, ans); // 【递归】:去 1 到 j-1 里继续选剩下的
path.removeLast(); // 【撤销选择】:也就是“回溯”
// 把 j 拿出来,为了下一轮循环能尝试别的数字
}
}
正序写法。
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
dfs(1, n, k, path, ans);
return ans;
}
private void dfs(int start, int n, int k, List<Integer> path, List<List<Integer>> ans) {
if(path.size() == k) {
ans.add(new ArrayList<>(path));
return;
}
if(n-start+1 < k - path.size()) return; // 区间剩余的数字 少于 需要凑够的数字
for(int j = start; j <= n; j++) {
path.add(j);
dfs(j+1, n, k, path, ans);
path.removeLast();
}
}
}
216. 组合总和Ⅲ
题解:
解法一。模仿题77的解法。剪枝有限。
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
dfs(1, k, n);
return ans;
}
// k: 总的k个数
// n: 最终需要的和
private void dfs(int start, int k, int n) {
if(path.size()==k) {
int sum = 0;
for(int num : path) sum+=num;
if(sum==n) ans.add(new ArrayList<>(path));
return;
}
if(9-start+1 < k-path.size()) return;
for(int i=start; i<=9; i++) {
path.add(i);
dfs(i+1, k, n);
path.removeLast();
}
}
}
解法二。修改dfs方法中的参数的语义。用来避免解法一中的反复求和的开销。
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
dfs(1, k, n);
return ans;
}
// remainCnt 还需要选几个数
// remainSum 还需要凑多少和
private void dfs(int start, int remainCnt, int remainSum) {
// 1. 剪枝:
// 剩下的候选数字个数 (9 - startIndex + 1) 如果小于 还需要选的个数
// 说明怎么选都不够了,直接返回
if(9-start+1 < remainCnt) return;
// 2. 终止条件:如果不需要再选数了
if(remainCnt==0) {
if(remainSum==0) ans.add(new ArrayList<>(path));
return;
}
// 3. 循环选择
for(int i=start; i<=9; i++) {
// 如果当前选的数 i 已经大于 还需要凑的和,那后面更大的数肯定也不行,直接断开
if(i>remainSum) break;
path.add(i);
dfs(i+1, remainCnt-1, remainSum-i);
path.removeLast();
}
}
}
17. 电话号码的字母组合
题解:
需要注意,这道题遍历的是String,而不是数字数组。然后依旧遵循 回溯的三部曲:做选择 -> 递归 -> 撤销选择
class Solution {
List<String> ans = new ArrayList<>();
StringBuilder path = new StringBuilder();
Map<Integer, String> num2chars = new HashMap<>();
public List<String> letterCombinations(String digits) {
// 0. 特殊情况处理:如果输入为空,直接返回空列表
if (digits == null || digits.length() == 0) {
return ans;
}
init(); // 初始化字典
char[] digitsChars = digits.toCharArray();
int[] nums = new int[digitsChars.length];
for(int i=0; i<digitsChars.length; i++) {
nums[i] = digitsChars[i] - '0'; // 利用 ASCII 码特性转 int
}
dfs(0, nums);
return ans;
}
private void init() {
num2chars.put(2, "abc");
num2chars.put(3, "def");
num2chars.put(4, "ghi");
num2chars.put(5, "jkl");
num2chars.put(6, "mno");
num2chars.put(7, "pqrs");
num2chars.put(8, "tuv");
num2chars.put(9, "wxyz");
}
private void dfs(int idx, int[] nums) {
// 1. 终止条件:如果 idx 走到了数组末尾,说明所有数字都处理完了
if(idx == nums.length) {
ans.add(path.toString());
return;
}
// 2. 取出当前这一层(这一个数字)对应的字母集
int currentNum = nums[idx];
String letters = num2chars.get(currentNum);
// 3. 核心循环:遍历的是“字母”,而不是“数字数组”
for(int i=0; i<letters.length(); i++) {
char ch = letters.charAt(i);
path.append(ch); // 做选择
dfs(idx+1, nums); // 递归下一层(处理下一个数字)
path.deleteCharAt(path.length()-1); // 回溯,撤销选择
}
}
}

799

被折叠的 条评论
为什么被折叠?



