今天我们来看几道下组合类型相关的回溯算法题,话不多说,直接上题。
组合
给定两个整数n和k,返回 1 ...n中所有可能的k个数的组合。题目来源:leetcode.77
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
这道题其实和求子集思路差不多,不过子集长度和给定的整数数组变为变量了,来看代码。
function combination(n,k) {
const result = []
function loop(path,start) {
if (path.length === k) { // 边界条件当path长度为k时 即达到题解要求
result.push([...path])
return
}
for(let i = start;i<=n; i++) { // 注意每次递归中循环的起始值
path.push(i)
loop(path,i+1)
path.pop()
}
}
loop([],1)
return result
}
combination(4,2)
对于一直跟着文章学过来的小伙伴来说,这道题还是挺简单的,主要判断一下边界条件和注意每次递归的时候循环的i初始值就好了。
组合总和 I
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。
题目来源:leetcode.39
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
直接上代码:
function combination(arr,target) {
const result = []
function loop(path,start) {
// 计算每一次path中元素的和 当和target相等时 即为题解
const sum = path.reduce((p, c) => {
return p+c
},0)
if (sum === target) {
result.push([...path])
return
}
if (sum > target) return // 超过target的就剪掉
for (let i=start; i<arr.length; i++) { //每次循环起始值设置为start 使选择下一个元素时只能从当前开始 这样就能去除结果中的相同解
path.push(arr[i])
loop(path,i)
path.pop()
}
}
loop([],0)
return result
}
combination([2,3,6,7],7)
这道题容易忽略的点在于循环内的初始值设置,如果每次循环从0开始的话,得到的答案中会有重复的,以上题为例,结果就会变成
[2,2,3],[2,3,2],[3,2,2],[7]
所以选择下一个元素时循环的起始值只能从当前的i开始。
思路都差不多,确定边界条件,然后再判断递归的循环内需不需要剪枝还有每次循环的初始值,确定好了这几点之后套模版就可以了。
组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用一次。
题目来源:leetcode.40
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
和上一题的区别是给定的数组中有重复元素了,而且每个元素只能使用一次,边界条件的话是不用变的,所以只需要思考一下剪枝和循环的初始值部分的实现。
function combination(arr,target) {
const result = []
arr.sort((a,b) => a-b)
function loop(path,start) {
// 边界部分逻辑不变
const sum = path.reduce((p,v) => {
return p + v
},0)
if(sum === target) {
result.push([...path])
return
}
if(sum > target) return
for(let i=start; i<arr.length; i++) {// 因为每个元素只能使用一次 所以下一个元素选择从下一位开始
// 剪枝条件:因为有重复元素 所以要去掉同层中已经出现过的元素
if (i>start && arr[i] === arr[i-1]) continue
path.push(arr[i])
loop(path, i+1)
path.pop()
}
}
loop([],0)
return result
}
combination([10,1,2,7,6,1,5],8)
这道题的难点在于剪枝条件,因为每个元素只能使用一次,所以要对同层已经出现过的元素进行剪枝arr[i] === arr[i-1],而同一路径不同层的重复元素可以使用i>start。这点能想出来的话这道题也就迎刃而解了。
组合总和 III
找出所有相加之和为n的k个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。题目来源:leetcode.216
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]跟
只不过把给定的数组和target变成可变的了,解题思路没有变,直接上代码。
function combination(n,k) {
const result = []
function loop(path,start) {
const sum = path.reduce((p,v) => {
return p + v
},0)
const length = path.length
if (sum === n && length === k) {
result.push([...path])
return
}
if (sum > n || length > k) return
for(let i=start; i<10; i++) {
path.push(i)
loop(path,i+1)
path.pop()
}
}
loop([],1)
return result
}
combination(9,3)
这道题基本没什么难度,判断好边界条件和循环的初始值之后直接套公式就行了。
今天组合相关的回溯算法题就到这告一段落了,提前祝大家清明愉快。