给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
提示:
1 <= n <= 20
1 <= k <= n
本题的常规解法在昨天的题解中已经写出
代码随想录刷题笔记 DAY 24 | 回溯算法理论基础 | 组合问题 No. 77
这里来讲解一下本题的剪枝优化
假设 n = 4
且 k = 4
for (int i = startIndex; i <= n; i++) {
path.add(i);
backtracking(n, k, i+1);
path.remove(path.size() - 1); // 回溯删除节点
}
当 startIndex == 2
的时候,后面能取得的数字为 3 4
即遍历完所有的取值数目也不可能达到题目要求的 k
所以对上面除了 1 2 3 4
这条线都应该通过剪枝去除,因为遍历它们没有意义。
当确定了一个 i
那这条路线能取得的最多的数字个数就确认了,也就是 n - i + 1
当取到这个节点的时候 path
内共有 path.size()
个元素,所以从这个路线中能取得的 最大 数目为
N m = n − i + 1 − p a t h . s i z e ( ) N_m = n - i + 1 - path.size() Nm=n−i+1−path.size()
如果这条路线能够取得总数 k
,那可以得出
N m ≥ k N_m \ge k Nm≥k
最终能够推出
i ≤ n − k + 1 − p a t h . s i z e ( ) i \le n - k + 1-path.size() i≤n−k+1−path.size()
这时候的 i
才是 有意义 的 i
所以 for
循环的终止条件应该改为上式
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k, 1);
return res;
}
public void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <= (n - k + path.size() + 1); i++) {
path.add(i);
backtracking(n, k, i+1);
path.remove(path.size() - 1);
}
}
}
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。
示例 3:
输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。
提示:
2 <= k <= 9
1 <= n <= 60
如果做过组合问题,这道题目就非常容易了,思路和剪枝操作都是完全相同的,就只需要注意一下递归的终点。
因为本题有两个限制条件
k
n
所以当判断出 sum > k
的时候也要记得结束递归
class Solution {
int temp = 0;
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(k, n, 1);
return res;
}
public void backtracking(int k, int n, int startIndex) {
if (temp > n) {
return;
}
if (path.size() == k && temp == n) {
res.add(new ArrayList(path));
}
for (int i = startIndex; i <= (9 - k + path.size() + 1); i++) {
path.add(i);
temp += i;
backtracking(k, n, i+1);
temp -= i;
path.remove(path.size() - 1);
}
}
}
题目链接
代码随想录题解
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
提示:
0 <= digits.length <= 4
digits[i]
是范围 ['2', '9']
的一个数字。先给出这道题的回溯解法:
在大多数的题目中回溯的作用就是提供一个层数可控的 for
循环来暴力解决这个问题,所以当某道题目没有思路的时候就先向 for
循环的方向思考。
❓ 这道题目用 for
循环应该如何解答呢?
假如说只按了两个数字,
2
和3
,首先将 数字映射为字符数组,再去求这 两个数组 的组合,那肯定很容易,两层for
循环就可以解决。for (String a : arr1) { for (String b : arr2) { } }
通过这样的形式就能求出所有的组合。
那三个按键、五个按键该怎么解决呢?
这就需要回溯法来解决这个问题了,大家遇到这道题没思路的原因很大概率是没接触过这种每层的分枝操作的 不是同一个数组
所以要做的就是在每层遍历完后去改变 for
循环中遍历的数组,这就是形成本题目要求的树形结构的方法。
可以在每次递归(前往下一层)的时候传递一个标识,用这个标识来确定 本层 遍历的数组是哪一个。
这里定为 index
,从 0
开始,直到遍历完 所有的按键结束,也就是 index > 按下的按键总数
如何通过 index
来定位到遍历的是 哪些 字母呢?
因为数字和字母存在着映射的关系,所以可以提前准备好所有的映射关系
static Map<Character, String[]> NumMap = new HashMap<>();
static {
NumMap.put('2', new String[]{"a", "b", "c"});
NumMap.put('3', new String[]{"d", "e", "f"});
NumMap.put('4', new String[]{"g", "h", "i"});
NumMap.put('5', new String[]{"j", "k", "l"});
NumMap.put('6', new String[]{"m", "n", "o"});
NumMap.put('7', new String[]{"p", "q", "r", "s"});
NumMap.put('8', new String[]{"t", "u", "v"});
NumMap.put('9', new String[]{"w", "x", "y", "z"});
}
接下来将输入的字符串转为字符数组,index
的意义就是这个字符数组的下标
globalArr = digits.toCharArray();
这样通过下标找到当前层是哪个数字,再通过 数字和字母的映射关系 就直到当前层遍历的字符串数组
class Solution {
char[] globalArr; // 存储传入的按键
StringBuilder temp = new StringBuilder(); // 路径变量
List<String> res = new ArrayList<>();
static Map<Character, String[]> NumMap = new HashMap<>();
static {
NumMap.put('2', new String[]{"a", "b", "c"});
NumMap.put('3', new String[]{"d", "e", "f"});
NumMap.put('4', new String[]{"g", "h", "i"});
NumMap.put('5', new String[]{"j", "k", "l"});
NumMap.put('6', new String[]{"m", "n", "o"});
NumMap.put('7', new String[]{"p", "q", "r", "s"});
NumMap.put('8', new String[]{"t", "u", "v"});
NumMap.put('9', new String[]{"w", "x", "y", "z"});
}
public List<String> letterCombinations(String digits) {
globalArr = digits.toCharArray();
if (globalArr.length == 0) {
return new ArrayList<>();
}
backtracking(0);
return res;
}
public void backtracking(int index) {
if (index > globalArr.length - 1) {
res.add(temp.toString());
return;
}
// 获取本层遍历的是哪个字符数组
String[] letterArr = NumMap.get(globalArr[index]);
for (String s : letterArr) {
temp.append(s);
backtracking(++index);
temp.deleteCharAt(temp.length() - 1);
index--;
}
}
}
其实这道题目我一开始做的时候使用的是另一种方法。
以 按键 234
为例,本题其实就是求 2
和 3
的所有组合 x
,再求 x
和 4
的所有组合。
所以先编写一个实现两两组合的代码,再通过遍历将所有的按键全部组合起来也能得到结果。
class Solution {
// 映射关系
static Map<Character, String[]> NumMap = new HashMap<>();
static {
NumMap.put('2', new String[]{"a", "b", "c"});
NumMap.put('3', new String[]{"d", "e", "f"});
NumMap.put('4', new String[]{"g", "h", "i"});
NumMap.put('5', new String[]{"j", "k", "l"});
NumMap.put('6', new String[]{"m", "n", "o"});
NumMap.put('7', new String[]{"p", "q", "r", "s"});
NumMap.put('8', new String[]{"t", "u", "v"});
NumMap.put('9', new String[]{"w", "x", "y", "z"});
}
public List<String> letterCombinations(String digits) {
char[] charArray = digits.toCharArray();
// 边界情况的处理:字符个数不足两个
if (charArray.length == 1) {
return Arrays.stream(NumMap.get(charArray[0])).toList();
} else if (charArray.length == 0) {
return new ArrayList<>();
}
// 将 temp 与其他字符依次两两组合
String[] temp = getTwoCombinations(NumMap.get(charArray[0]), NumMap.get(charArray[1]));
for (int i = 2; i < charArray.length; i++) {
temp = getTwoCombinations(temp, NumMap.get(charArray[i]));
}
return Arrays.stream(temp).toList();
}
/**
实现两两组合
*/
public String[] getTwoCombinations(String[] a, String[] b) {
String[] res = new String[a.length * b.length];
int n = 0;
for (String string : a) {
String temp = string;
for (String s : b) {
temp += s;
res[n++] = temp;
temp = string;
}
}
return res;
}
}