中等难度。本来觉得很无聊,但是看到题解中说到剪枝算法,想看一下所以就干脆解一遍。
读题
给定一个无重复元素的数组 candidates 和一个目标数 target ,
找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这题看上去非常眼熟,因为之前做过“两数之和”,“三数之和”,这题的区别是不限制多少个数字,所以要穷举。
没有足够的限制条件,所以只能递归了。那么问题就转化为如何优化递归性能了。
实现
func combinationSum(candidates []int, target int) [][]int {
if len(candidates) == 0 {
return [][]int{}
}
// 注意每次启动时都要初始化所有全局变量
cd = candidates
len0039 = len(cd)
sort.Ints(cd)
temp0039 = make([]int, target/cd[0]+1)
result0039 = [][]int{}
recurCombinationSum(0, 0, target)
return result0039
}
var cd []int
var len0039 int
var temp0039 []int
var result0039 [][]int
func recurCombinationSum(cdPos, tempPos int, target int) {
for n := 0; cdPos < len0039; cdPos++ { // 为了可读性,将n单独作为变量
n = cd[cdPos]
if n > target {
return
}
temp0039[tempPos] = n
if n == target {
newSolution := make([]int, tempPos+1)
copy(newSolution, temp0039)
result0039 = append(result0039, newSolution)
return
} else {
recurCombinationSum(cdPos, tempPos+1, target-n)
}
}
}
优化思路就是,把一些公共变量放在堆上作为全局变量(而不是作为递归时的参数)(这种方法是线程不安全的,当然改进也很简单,封装为类就好了)
(回溯算法)每次递归时,在temp数组上前进一位,然后target减少;只有找到相等的情况才append,大于就中止,小于就继续递归。
(剪枝)在candidates数组上也要记录位置,防止出现[2,2,3], [2,3,2], [3,2,2]这样的重复解。
提交成绩:
执行用时 :8 ms, 在所有 Go 提交中击败了70.93%的用户
内存消耗 :4.2 MB, 在所有 Go 提交中击败了80.00%的用户
测试用例:
{
args: args大专栏 LeetCode[39]: 组合总和span>{[]int{2, 3, 6, 7}, 7},
want: [][]int{
{2, 2, 3}, {7},
},
},
{
args: args{[]int{2, 3, 5}, 8},
want: [][]int{
{2, 2, 2, 2}, {2, 3, 3}, {3, 5},
},
},
{
name: "无解",
args: args{[]int{2, 3, 5}, 1},
want: [][]int{
},
},
{
name: "target在中间",
args: args{[]int{1, 4, 5}, 3},
want: [][]int{
{1, 1, 1},
},
},
稍微改进
忽然想到在命中的时候(叶子节点)就已经可以return了。然后再去掉那个递归中便于阅读的变量n:
func recurCombinationSum(cdPos, tempPos int, target int) {
for ; cdPos < len0039; cdPos++ {
if cd[cdPos] > target {
return
}
temp0039[tempPos] = cd[cdPos]
if cd[cdPos] == target {
newSolution := make([]int, tempPos+1)
copy(newSolution, temp0039)
result0039 = append(result0039, newSolution)
return
} else {
recurCombinationSum(cdPos, tempPos+1, target-cd[cdPos])
}
}
}
提交成绩:
执行用时 :4 ms, 在所有 Go 提交中击败了94.83%的用户
内存消耗 :4.3 MB, 在所有 Go 提交中击败了80.00%的用户
其他思路
看了题解,发现我自己的思路就是所谓的回溯+剪枝,其实思路很简单,加上名字好像就变得高大上了。
还有所谓的深度优先,其实对于这题应该只有深度优先,广度优先应该不可行。
然后还看到奇葩用动态规划来解??光是想想就很难受,真亏他做得出来……