从一个乱序数组中选出 n 个元素,让它们相加之和等于给定的数 sum
n >= 2
双指针解法
{
/**
* @description 在 nums 数组内,从下标为 start 的位置开始,选出 n 个数,这些数的和要等于 sum
* ! 前提:nums 必须是有序数组
* n 为正整数,且 n >= 2
* n = 2 时,T=O(nlogn)
* n > 2 时,T=O(n ^ (n - 1))
*/
const _nSum = (nums: number[], start: number, n: number, sum: number) => {
const length: number = nums.length
if (start >= length) return []
const result: number[][] = []
// base case:两数之和
if (n === 2) {
let left: number = start
let right: number = length - 1
let leftValue: number
let rightValue: number
let sum: number
while (left < right) {
leftValue = nums[left]
rightValue = nums[right]
sum = leftValue + rightValue
if (sum === sum) {
result.push([leftValue, rightValue])
while (left < right && nums[left] === leftValue) left += 1
while (left < right && nums[right] === rightValue) right -= 1
} else if (sum < sum) {
while (left < right && nums[left] === leftValue) left += 1
} else if (sum > sum) {
while (left < right && nums[right] === rightValue) right -= 1
}
}
} else {
// n > 2
for (let i = start; i < length; ) {
const current = nums[i]
const sub: number[][] = _nSum(nums, i + 1, n - 1, sum - current)
sub.length > 0 && sub.map(a => result.push([current].concat(a)))
while (i < length && nums[i] === current) i += 1
}
}
return result
}
const nSum = (nums: number[], n: number, target: number) => {
if (n < 2 || n > nums.length) return []
// 必须传入 (a, b) => a - b
// nums.sort() 只会让元素先被转为字符串,然后根据字典顺序排序。举例:10 排在 2 之前
// 所以必须传入 (a, b) => a - b,才能真正让数组元素升序排序
nums.sort((a, b) => a - b)
return _nSum(nums, 0, n, target)
}
// 以下是测试代码
// 两数之和
console.log(nSum([2, 7, 11, 15], 2, 9)) // [2, 7]
console.log(nSum([1, 1, 1, 2, 2, 3, 3], 2, 4)) // [[1, 3], [2, 2]]
console.log(nSum([1, 3, 1, 2, 2, 3], 2, 4)) // [[1, 3], [2, 2]]
console.log(nSum([1, 2, 3, 4], 2, 5)) // [ [ 1, 4 ], [ 2, 3 ] ]
// 三数之和
console.log(nSum([-1, 0, 1, 2, -1, -4], 3, 0)) // [[-1, -1, 2], [-1, 0, 1]]
console.log(nSum([], 3, 0)) // []
console.log(nSum([0], 3, 0)) // []
console.log(nSum([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 5)) // []
console.log(nSum([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 15))
// [
// [ 1, 5, 9 ],
// [ 1, 6, 8 ],
// [ 2, 4, 9 ],
// [ 2, 5, 8 ],
// [ 2, 6, 7 ],
// [ 3, 4, 8 ],
// [ 3, 5, 7 ],
// [ 4, 5, 6 ]
// ]
// 四数之和
console.log(nSum([1, 0, -1, 0, -2, 2], 4, 0)) // [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
console.log(nSum([], 4, 0)) // []
console.log(nSum([0], 4, 0)) // []
console.log(nSum([1, 2, 3, 4, 5, 6, 7, 8, 9], 4, 15))
// [
// [ 1, 2, 3, 9 ],
// [ 1, 2, 4, 8 ],
// [ 1, 2, 5, 7 ],
// [ 1, 3, 4, 7 ],
// [ 1, 3, 5, 6 ],
// [ 2, 3, 4, 6 ]
// ]
}
回溯算法
{
const _nSum = (
array: number[],
start: number,
n: number,
sum: number,
_result: number[], // 某个可行解
result: number[][] // 全部可行解
) => {
if (
_result.length === n &&
_result.reduce(
(accumulator: number, current: number) => accumulator + current,
0
) === sum
) {
return result.push([..._result])
}
for (let i = start; i < array.length; i++) {
_result.push(array[i])
_nSum(array, i + 1, n, sum, _result, result)
_result.pop()
}
}
const nSum = (array: number[], n: number, sum: number) => {
const result: number[][] = []
_nSum(array, n, sum, 0, [], result)
return result
}
// 以下是测试代码
// 两数之和
console.log(nSum([2, 7, 11, 15], 2, 9)) // [2, 7]
console.log(nSum([1, 1, 1, 2, 2, 3, 3], 2, 4))
// [
// [ 1, 3 ], [ 1, 3 ],
// [ 1, 3 ], [ 1, 3 ],
// [ 1, 3 ], [ 1, 3 ],
// [ 2, 2 ]
// ]
console.log(nSum([1, 3, 1, 2, 2, 3], 2, 4)) // [ [ 1, 3 ], [ 1, 3 ], [ 3, 1 ], [ 1, 3 ], [ 2, 2 ] ]
console.log(nSum([1, 2, 3, 4], 2, 5)) // [ [ 1, 4 ], [ 2, 3 ] ]
// 三数之和
console.log(nSum([-1, 0, 1, 2, -1, -4], 3, 0)) // [ [ -1, 0, 1 ], [ -1, 2, -1 ], [ 0, 1, -1 ] ]
console.log(nSum([], 3, 0)) // []
console.log(nSum([0], 3, 0)) // []
console.log(nSum([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 5)) // []
console.log(nSum([1, 2, 3, 4, 5, 6, 7, 8, 9], 3, 15))
// [
// [ 1, 5, 9 ],
// [ 1, 6, 8 ],
// [ 2, 4, 9 ],
// [ 2, 5, 8 ],
// [ 2, 6, 7 ],
// [ 3, 4, 8 ],
// [ 3, 5, 7 ],
// [ 4, 5, 6 ]
// ]
// 四数之和
console.log(nSum([1, 0, -1, 0, -2, 2], 4, 0)) // [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
console.log(nSum([], 4, 0)) // []
console.log(nSum([0], 4, 0)) // []
console.log(nSum([1, 2, 3, 4, 5, 6, 7, 8, 9], 4, 15))
// [
// [ 1, 2, 3, 9 ],
// [ 1, 2, 4, 8 ],
// [ 1, 2, 5, 7 ],
// [ 1, 3, 4, 7 ],
// [ 1, 3, 5, 6 ],
// [ 2, 3, 4, 6 ]
// ]
}
总结
| 解法 | 双指针 | 回溯 |
|---|---|---|
| 是否需要排序 | 是 | 否 |
| 算法复杂度 | n 等于 2: nlog(n);n 大于 2:n ^ (n - 1) | 组合数 n 中选 k 个 |
| 是否能够过滤仅是排列顺序不同但是元素相同的组合(注 1) | 能 | 不能 |
注 1:将 [1, 3] 和 [3, 1] 视为相同的解,最终结果只保留其中一个