Skip to content
On this page

组合

Question

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

思路

77.组合

每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围

图中可以发现n相当于树的宽度,k相当于树的深度

那么如何在这个树上遍历,然后收集到我们要的结果集呢?

图中每次搜索到了叶子节点,我们就找到了一个结果

相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。

js
// if (终止条件) {
//     存放结果;
//     return;
// }

// for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
//     处理节点;
//     backtracking(路径,选择列表); // 递归
//     回溯,撤销处理结果
// }
js
var combine = function(n, k) {
    const res = []  // 结果[][]
    const path = [] // 每次递归的结果[]
    // startIndex :每次递归的起使位置
    const backtracking = (n, k, startIndex)=>{
        if(path.length===k){		// 终止条件
            res.push([...path])		// 存放结果
            return
        }
        for(let i=startIndex;i<=n;i++){	// 本层集合中元素
            path.push(i)				// 处理节点
            backtracking(n,k,i+1)		// 递归
            path.pop()					//回溯
        }
        console.log(res,path)
    }
    backtracking(n,k,1)
    return res
};
image-20230615071614872

这样做是没错的,但是时间上超限了

剪枝优化

77.组合4

图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。

所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

接下来看一下优化过程如下:

  1. 已经选择的元素个数:path.size();
  2. 还需要的元素个数为: k - path.size();
  3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历

为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。

优化之后的代码:

js
var combine = function(n, k) {
    const res = []  //结果[][]
    const path = [] //每次递归的结果[]
    // startIndex :每次递归的起使位置
    const backtracking = (n, k, startIndex)=>{
        if(path.length===k){
            res.push([...path])
            return
        }
        for(let i=startIndex;i<= n-(k-path.length) + 1;i++){	//剪枝优化
            path.push(i)
            backtracking(n,k,i+1)
            path.pop()
        }
    }
    backtracking(n,k,1)
    return res
};