zoukankan      html  css  js  c++  java
  • 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%的用户
    

    其他思路

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

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

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

  • 相关阅读:
    前端开发者应该知道的 CSS 小技巧
    css3制作六边形图片
    css3 绘制优惠券
    flex css 布局
    js 微信分享
    JS判断移动设备最佳方法 并实现跳转至手机版网页
    ajax 提交成功页面跳转问题
    css相关tips
    无阻塞加载和defer、async
    常用排序算法
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12286312.html
Copyright © 2011-2022 走看看