一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
题目
给定两个整数 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 <= 201 <= k <= n
思考
这是一道有意思的题目,难度中等。
在思考如何解答这道题时,可以想到的是,如果k=2,我们可以借助两层for循环去遍历所有可能的组合,代码如下:
let n = 4;
for (let i = 1; i <= n; i++) {
for (let j = i + 1; j <= n; j++) {
console.log(i, j)
}
}
这种思路有明显的局限性,如果k=3那么就需要3层for循环,随着k的增大我们无法写出符合条件的代码了...
我们可以借助回溯法,使用递归来解决嵌套层数的问题,将问题抽象为树形结构(N叉树),这样就容易理解很多了。由于之前没怎么接触这类问题,这里参考了官方答案去学习解题思路的。借用liweiwei1419的示意图,回溯法的思路如下所示:
这里我们逐个考虑数组[1, n]中的元素是否处于组合中,也就是深度优先遍历算法(DFS,即 Depth First Search)。对于不符合条件的情况我们通过剪枝去优化掉,合法的组合我们记录下来。
解答
方法一:递归实现组合型枚举
// 返回范围 [1, n] 中所有可能的 k 个数的组合
var combine = function(n, k) {
const ans = [];
const dfs = (cur, n, k, temp) => {
// 剪枝:temp 长度不为 k 时返回
if (temp.length + (n - cur + 1) < k) {
return;
}
// 记录合法的答案
if (temp.length == k) {
ans.push(temp);
return;
}
// 选择当前位置,记录cur后继续递归
dfs(cur + 1, n, k, [...temp, cur]);
// 不选择当前位置,跳过cur后继续递归
dfs(cur + 1, n, k, temp);
}
dfs(1, n, k, []);
return ans;
}