1.给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
2.
class Solution {
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
if (k <= 0 || n <= 0) {
return res;
}
List<Integer> track = new LinkedList<>();
backtrack(n, k, 1, track);
return res;
}
void backtrack(int n, int k, int start, List<Integer> track) {
// 到达树的底部
if (k == track.size()) {
res.add(new LinkedList<>(track));
return;
}
// 注意 i 从 start 开始递增
for (int i = start; i <= n; i++) {
// 做选择
track.add(i);
backtrack(n, k, i + 1, track);
// 撤销选择
track.remove(track.size() - 1);
}
}
}
3.
List<List<Integer>> res = new LinkedList<>();
这一行创建了一个列表 res,用于存储最终的组合结果。这个列表是一个二维列表,其中每个子列表都表示一个符合条件的组合。
public List<List<Integer>> combine(int n, int k) {
if (k <= 0 || n <= 0) {
return res;
}
List<Integer> track = new LinkedList<>();
backtrack(n, k, 1, track);
return res;
}
这是公共函数 combine,它接受输入 n 和 k。首先,它检查输入是否有效,如果 k 或 n 不大于 0,就立即返回空列表 res。然后,它创建一个空列表 track 用于存储当前的组合,接着调用 backtrack 函数来生成组合。
backtrack(n, k, 1, track);
这行代码是在 combine 方法中调用 backtrack 方法的起始点,它启动了回溯算法的递归过程来生成组合。
具体解释如下:
n表示数字的范围,即从 1 到n。k表示要生成的组合的长度。1是start参数的初始值,表示从数字 1 开始尝试生成组合。track是一个空的列表,用于记录当前已选的数字组合,初始为空。
通过调用 backtrack(n, k, 1, track);,代码开始从数字 1 开始尝试生成长度为 k 的组合。随着回溯递归的进行,不断地选择数字、递归进入下一层,直到满足条件或无法再选择数字时,回溯退回到上一层,继续尝试其他可能的组合。这个过程会在递归中不断重复,直到生成了所有符合条件的组合,并将它们添加到 res 结果列表中。
所以,backtrack(n, k, 1, track); 是开始整个组合生成过程的触发点。
当你输入 n = 4 和 k = 2,并调用 backtrack(n, k, 1, track);,代码将尝试生成从 1 到 4 的数字中长度为 2 的所有可能组合。
初始情况下,track 是一个空列表,start 的值是 1,表示从数字 1 开始尝试生成组合。
下面是生成组合的过程:
backtrack(n, k, 1, track);开始执行,此时track为空,start是 1。- 在
backtrack方法中,从 1 到 4 的数字依次尝试选择。- 当选择数字 1 时,
track变为 [1]。 - 接着,递归调用
backtrack(n, k, 2, track);,此时start变为 2。- 在第二层递归中,尝试选择数字 2,生成组合 [1, 2]。
- 这里
k达到了 2,符合条件,所以将 [1, 2] 添加到结果列表res中。 - 然后递归返回,回到第一层递归。
- 继续尝试数字 3 和数字 4,但都不满足条件,所以不进行任何操作。
- 回到第一层递归,继续尝试数字 2、3、4,但都不满足条件。
- 当选择数字 1 时,
- 最终,
backtrack(n, k, 1, track);完成,res中包含了所有符合条件的组合,即 [[1, 2]]。
所以,对于输入 n = 4 和 k = 2,backtrack(n, k, 1, track); 生成的组合是 [[1, 2]]。这是从 1 到 4 的数字中长度为 2 的所有可能组合之一。
void backtrack(int n, int k, int start, List<Integer> track) {
// 到达树的底部
if (k == track.size()) {
res.add(new LinkedList<>(track));
return;
}
// 注意 i 从 start 开始递增
for (int i = start; i <= n; i++) {
// 做选择
track.add(i);
backtrack(n, k, i + 1, track);
// 撤销选择
track.remove(track.size() - 1);
}
}
这是回溯函数 backtrack。它的目标是生成所有符合条件的组合。
-
如果已经选择了 k 个数字,也就是
k == track.size(),那么当前的track列表表示一个符合条件的组合,将它复制到res中,并返回。 -
否则,它使用一个循环遍历从
start到n的数字。在每一次循环中,它将数字i添加到track列表中,然后递归调用自身来继续生成下一个数字的组合。递归调用时,start更新为i + 1,表示下一个数字的选择范围。 -
最后,需要撤销选择,即从
track中移除最后一个添加的数字,以便尝试下一个数字。
通过这个递归和回溯的过程,代码将逐步生成范围 [1, 4] 中所有可能的 2 个数字的组合,并将它们存储在 res 列表中。最终,res 中将包含所有符合条件的组合,示例中的输出即是 res 的内容。
对于输入 n = 4 和 k = 2,给定的 backtrack 方法将尝试生成从 1 到 4 的数字中长度为 2 的所有可能组合。让我们按步骤来解释它的执行过程:
- 初始时,
track是一个空列表,start的值是 1。 - 在第一次调用
backtrack(n, k, 1, track);时,进入递归。k还不等于track.size(),所以不会执行添加到res的操作。- 进入循环,从
start(1)到n(4)依次尝试选择数字。- 当选择数字 1 时,
track变为 [1]。- 然后递归调用
backtrack(n, k, 2, track);,此时start变为 2。- 在第二层递归中,再次进入循环,从 2 开始尝试选择数字。
- 当选择数字 2 时,
track变为 [1, 2]。 - 此时
k(2)等于track.size(),因此将 [1, 2] 添加到结果列表res中。 - 然后递归返回,回到第一层递归。
- 接着尝试数字 3 和数字 4,但它们不满足条件,所以不会进行任何操作。
- 当选择数字 2 时,
- 在第二层递归中,再次进入循环,从 2 开始尝试选择数字。
- 第一层递归继续,尝试数字 3 和数字 4,但它们也不满足条件,所以不会进行任何操作。
- 然后递归调用
- 当选择数字 1 时,
- 第一层递归的循环结束,回到最初的状态,也就是空的
track。
backtrack(n, k, 1, track);完成,res中包含了所有符合条件的组合,即 [[1, 2]]。
所以,对于输入 n = 4 和 k = 2,backtrack(n, k, 1, track); 生成的组合是 [[1, 2]],这是从 1 到 4 的数字中长度为 2 的所有可能组合之一。