LeetCode[39]: 组合总和

中等难度。本来觉得很无聊,但是看到题解中说到剪枝算法,想看一下所以就干脆解一遍。

读题

给定一个无重复元素的数组 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%的用户

其他思路

看了题解,发现我自己的思路就是所谓的回溯+剪枝,其实思路很简单,加上名字好像就变得高大上了。

还有所谓的深度优先,其实对于这题应该只有深度优先,广度优先应该不可行。

然后还看到奇葩用动态规划来解??光是想想就很难受,真亏他做得出来……

猜你喜欢

转载自www.cnblogs.com/lijianming180/p/12286312.html
今日推荐