77.Combinations

Given two integers n and k, return all possible combinations of k numbers out of 1 ... n. You may return the answer in any order.

Example 1:

Input: n = 4, k = 2
Output:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

Example 2:

Input: n = 1, k = 1
Output: [[1]]

Constraints:

  • 1 <= n <= 20
  • 1 <= k <= n

analyze

n: 1, 2, 3, 4 k: 2

  • 1
    • 2
    • 3
    • 4
  • 2
    • 3
    • 4
  • 3
    • 4

组合问题中, 不同顺序的解为同一个。比如 [1, 2], [2, 1] 为相同解。

此外可以发现规律, 保留所有增序排列的解即为组合的解。

/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
  const result = []
  backTrace(n, k, 1, [], result)
  return result
};

var backTrace = function(n, k, index, temp, result) {
  if (temp.length === k) {
    if (ifIncrease(temp)) {
      result.push([...temp])
    }
    return
  }

  for (let i = index; i <= n; i++) {
    temp.push(i)
    backTrace(n, k, index + 1, temp, result)
    temp.pop()
  }
}

var ifIncrease = function(arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] >= arr[i + 1]) return false
  }
  return true
}

提交, 此时报超时的错误。经排查发现, 截支的地方存在问题, 当前在 temp 中的值被重复使用了。修改如下

- backTrace(n, k, index + 1, temp, result)
+ backTrace(n, k, i + 1, temp, result)
/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
  const result = []
  backTrace(n, k, 1, [], result)
  return result
};

var backTrace = function(n, k, index, temp, result) {
  if (temp.length === k) {
    result.push([...temp])
    return
  }

  for (let i = index; i <= n; i++) {
    temp.push(i)
    backTrace(n, k, i + 1, temp, result)
    temp.pop()
  }
}

回溯法剪支

还是以 n = 5, k = 3 为例, 上述代码中, 实际遍历的顺序如下:

* 1
  * 2
    * 3
    * 4
    * 5
  * 3
    * 4
    * 5
  * 4
    * 5
  * 5 // 减支
* 2
  * 3
    * 4
    * 5
  * 4
    * 5
  * 5 // 减支
* 3
  * 4
    * 5
  * 5 // 减支
* 4 // 减支

此时, 当第一列的值到达 3 时, 达到搜索上界, 3, 4, 5 是其中一个解。如果再往后遍历, 4 作为解的开头, 则位数达不到 3 位, 不满足要求了。同理第二列的搜索上界为 4。第三列的搜索上界为 5。接着结合代码进行优化:

/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
  const result = []
  backTrace(n, k, 1, [], result)
  return result
};

var backTrace = function(n, k, index, temp, result) {
  if (temp.length === k) {
    result.push([...temp])
    return
  }

  // 题目可以转化为从搜索上界到 n 中要取的数字长度为 (k - temp.length)。
  // 可以得出搜索上界为: n - (k - temp.length) + 1
  for (let i = index; i <= n - (k - temp.length) + 1; i++) {
    temp.push(i)
    backTrace(n, k, i + 1, temp, result)
    temp.pop()
  }
}