「前端刷题」77. 组合

138 阅读1分钟

「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战」。

题目

链接:leetcode-cn.com/problems/co…

给定两个整数 nk,返回范围 [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

解题思路

思路1

  • 从起点开始遍历,若当前路径达到要求,拷贝一份,推入答案数组
  • 否则,遍历起点到n,将当前i加入路径
  • 更新起点,继续遍历
  • 记得遍历完,要将路径弹出一个元素

代码

const combine = (n, k) => {
    // 最后的答案
    const res = [];
    // start是起点,path是路径的数组
    const search = (start, path) => {
        if (path.length === k) {
            // 如果路径长度为k,满足题目要求了
            // 将path推入答案res
            res.push(path.slice());
            return;
        }
        for (let i = start; i <= n; i++) {
            // 每找一个,推入path一个
            path.push(i);
            // 从i+1,接着path继续找
            search(i + 1, path);
            // 上一级找完,path尾巴要拿出来
            path.pop();
        }
    };
    search(1, []);
    return res;
};

思路2

标准的回溯问题,套用代码模版

result = [];
function backtrack (path, list) {
    if (满足条件) {
        result.push(path);
        return
    }
    
    for () {
        // 做选择(前序遍历)
        backtrack (path, list)
        // 撤销选择(后续遍历)
    }
}

代码

var combine = function(n, k) {
    if (k <= 0 || n <= 0) return res;
    const ans = [];
    const backtrack = (n, k, start, track) => {
        if (k === track.length) {
            ans.push(track)
            return
        }
        for (let i = start; i <= n; i++) {
            track.push(i)
            backtrack(n, k, i + 1, [...track])
            track.pop()
        }
    }
    backtrack(n, k, 1, []);
    return ans;
};

我们把这个过程当成一个树,这样可以考虑用DFS来解,就是求每条路径,代码如下:

var combine = function(n, k) {
    const ans = [];
    const dfs = (cur, n, k, temp) => {
        // 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp
        if (temp.length + (n - cur + 1) < k) {
            return;
        }
        // 记录合法的答案
        if (temp.length == k) {
            ans.push(temp);
            return;
        }
        // 考虑选择当前位置
        dfs(cur + 1, n, k, [...temp, cur]);
        // 考虑不选择当前位置
        dfs(cur + 1, n, k, temp);
    }
    dfs(1, n, k, []);
    return ans;
};

思路3

比如 1,2,3,4 取两个数,我们的思考是

  • 先取一个1,得到 12 13 14
  • 再取一个2,得到 23 24
  • 再取一个3,得到 34
  • 总结出一个规律,就是每次位移一个单位,从后边取 k-1个

代码

function pick(arr, start, num) {
  let r = [];
  if (num === 1) {
    for (let a = start; a < arr.length; a++) {
      r.push([arr[a]])
    }
    return r;
  }
  for (let i = start; i < arr.length; i++) {
    let startValue = arr[i];
    let rest = pick(arr, i + 1, num - 1);
    for (let k = 0; k < rest.length; k++) {
      r.push([startValue, ...rest[k]])
    }
  }
  return r;
}

var combine = function (n, k) {
  let nums = [];
  for (let a = 1; a <= n; a++) {
    nums.push(a)
  }
  return pick(nums, 0, k);
};