LeetCode之HOT100--039 组合总和

168 阅读2分钟

这是我参与11月更文挑战的19天,活动详情查看:2021最后一次更文挑战

前言

一直都计划学习数据结构与基本算法,但是平时都看一阵停一阵。现在决心坚持下去,我准备从LeetCode的HOT100开始,每天完成1~2道习题,希望通过这种方式养成持续学习的习惯。因为我是做iOS开发的,主要是用Objective-C语言,最近也在学习Swift,所以本系列的题解都将使用swift语言完成,本文更新的是LeetCode中HOT100的第19题039 组合总和。

题目

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
示例 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]]

示例 3:

输入: candidates = [2], target = 1
输出: []

示例 4:

输入: candidates = [1], target = 1
输出: [[1]]

示例 5:

输入: candidates = [1], target = 2
输出: [[1,1]]

提示:

1. 1 <= candidates.length <= 30
2. 1 <= candidates[i] <= 200
3. candidate 中的每个元素都是独一无二的。
4. 1 <= target <= 500

分析

本题其实有点类似全排列,从数组中找出满足条件的组合,再加上每一个元素可以出现多次,所以第一想法就是采用回溯法。
回溯法的特点是递归调用,所以这里涉及到的几个问题就是递归终止条件,递归分支,并且因为要求结果集中的组合不能重复,所以要注意剪枝(本题解中采用贪心算法的思路来实现剪枝和去重)。

1. 回溯终止条件:当前组合中的总和 >= 目标值,等于则保存到结果集
2. 回溯步骤:依次将候选列表的元素添加到组合中,进行回溯
3. 去重剪枝:这一部分包含两个内容,一是将候选列表从大到小进行排序,二是在回溯的时候从当前下标开始,不再重新添加前面的元素

题解

class KLLC039 {

    func combinationSum(_ candidates: [Int], _ target: Int) -> [[Int]] {
        //候选列表从大到小排序
        let nums = candidates.sorted(by: >)
        //初始化结果集,当前组合列表
        var result = [[Int]]()
        var curConbine = [Int]()
        //开始调用回溯法
        backTrace(nums, target, 0, 0, &curConbine, &result)
        
        return result
    }
    
    
    /// 回溯法
    ///   - candidates: 候选数组
    ///   - target: 目标值
    ///   - curIndex: 当前加入的元素对应的下标
    ///   - curSum: 当前加入元素的总和
    ///   - curConbine: 当前加入元素集合
    ///   - result: 结果集
    func backTrace(_ candidates: [Int], _ target: Int, _ curIndex:Int, _ curSum:Int, _ curConbine:inout [Int], _ result:inout [[Int]]) {
    
        if curSum > target { return }

        //如果当前元素总和 等于 目标值,则将元素组合保存到结果集中
        if curSum == target {
            let combine = curConbine
            result.append(combine)
        }

        for (index, item) in candidates.enumerated() {
            //不允许加入 上一次加入元素 之前的元素。类似贪心算法
            if curSum + item <= target && index >= curIndex {
                //加入元素
                curConbine.append(item)
                //回溯
                backTrace(candidates, target, index, curSum + item, &curConbine, &result)
                //删除元素,进行下一个元素测试
                curConbine.removeLast()
            }
        }
    }
}