一起刷LeetCode——组合(递归/回溯+剪枝)

119 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。

来源:力扣(LeetCode) 链接:leetcode.cn/problems/co…

分析

  • 在刷了这么多题目后,应该不会有人用暴力解法了吧,几个数就几重循环,肯定是不符合题意的
  • 看到组合,可以想到排列组合公式C(n,k) = C(n-1,k)+C(n-1,k-1),回忆一下:从n个数中选k个数的所有组合以选了第n个数和没选第n个数两类,如果没选第n个数,那么就是从n-1个数中选k个,如果选了第n个数,那么就是从n-1个数中选k-1个,递归公式出来了,自然可以是用递归来解

代码

/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k, numsArr = Array(n).fill(1).map((n,i)=> n+i)) {
    if(k === 0) return [[]]
    if(numsArr.length < k) return []
    let first = numsArr[0]
    let otherCombos = combine(n,k-1, numsArr.slice(1))
    let hasFirstCombos = combine(n-1, k-1, numsArr.slice(1))
    let noFirstCombos = []
    for(let combo of otherCombos){
        noFirstCombos.push([first,...combo])
    }
    return [...noFirstCombos, ...hasFirstCombos]
};
  • 可以回想之前在学组合这部分的时候,在没有公式之前,我们是通过画图的方式来数组合,比如有4个节点分别是[1,2,3,4],选2个数,根节点作为开始,之后会生成4个分支,分别是1,2,3,4,在1这个子分支下,第二个数可能是2,3,4,以此类推,这样分析得到的组合就是一个树的形态,n是树的宽度,k是树的深度,这个方法就是回溯法,是暴力求解的一种
  • 在求组合的时候,会发现有重复的结果,在回溯的时候做了多余的操作,这些多余的操作在树上就像是多余的树枝,因此回溯法的优化叫剪枝法,即去掉不需要搜索的子分支

代码

var combine = function(n,k){
    const res = []
    const backtracking = (current,startNum,k)=>{
        if(n-startNum +1 < k) return
        if(k === 0) return res.push(current)
        for(let i=startNum;i<=n;i++){
            const newCurrent = [...current]
            newCurrent.push(i)
            backtracking(newCurrent, i+1, k-1)
        }
    }
    backtracking([],1,k)
    return res
}

总结

  • 这是一道比较经典的回溯算法的题
  • 人生路上也是这样,记得来时的路,记得路上的坎儿,不断的向前探索,才能最快的找到一条适合自己的路
  • 今天也是有收获的一天