【举一反三】力扣刷题-组合总和(Python 实现)

快速通道

39. 组合总和
40. 组合总和 II
216. 组合总和 III
377. 组合总和 Ⅳ

前言

最近喜欢写举一反三系列,看这个也算个系列就写了。这个系列主要是回溯、枚举还有剪枝,也是非常常用的方法。

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target
的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 

来源:力扣(LeetCode)

解题

大体意思就是数组中的数字可以使用多次,但是最终结果不能有重复的列表。
可能存在结果为同一个数,比如【2,2,2,2】,很明显枚举数组中的每个数,因为给定数组中不存在重复数据,直接遍历数组并回溯+剪枝 是不会出现重复结果的。

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        # 回溯 + 剪枝
        self.rec = []
        n = len(candidates)
        def backstrack(sumnum, curqu, startidx):
            if sumnum > target:
                return
            if sumnum == target:
                self.rec.append(curqu)
            for i in range(startidx,n):
                backstrack(sumnum + candidates[i], curqu + [candidates[i]], i)
        backstrack(0, [], 0)
        return self.rec
40. 组合总和 II

相对于第一题,该题数组数据存在重复数,然后只能从该数组中取数(不能像上一题一样取同一个数了),最终结果还是一样,没有重复列表。

解题

这一题肯定还是需要枚举,唯一的难点是如何去掉重复的结果呢,比较常见的思路是对结果进行去重(列表不能哈希,可以尝试其他办法),不过这种性能比较差,能不能通过剪枝的办法呢。
比如【1,2,2,3】这个列表,我想要枚举【1,2,3】,在枚举到第二个2 的时候我应该想到进行剪枝。再比如【2,2,3】,我想要枚举【2,3】,那么我在枚举了第二个2的时候同样需要剪枝。
就是我在枚举的时候如果这次枚举跟上次是一样的时候就没必要了,也就是说我枚举了同一个数的时候第二个数是没有必要的,如何表达呢?

  candidates[i] = candidates[i-1] 这样?

最终的结果是把数组去重了,我如何枚举出【1,2,2】呢,我在已经枚举【1,2】的情况下,我还想枚举第三个数,那也就是说我在第一次枚举的时候,如果枚举的第一个数跟上一个数一样的时候是可以枚举的。

if i > idx and candidates[i] == candidates[i-1]:
	# 剪枝
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        # 枚举 不过因为不能重复,需要剪枝
        rec = []
        n = len(candidates)
        candidates = sorted(candidates)
        def dfs(sumn, curqu, idx):
            if sumn == target:
                rec.append(curqu.copy())
                return
            if sumn > target or idx >= n:
                return

            # 枚举 + 剪枝
            for i in range(idx, n):
                if i > idx and candidates[i] == candidates[i-1]:
                    continue
                dfs(sumn + candidates[i], curqu + [candidates[i]], i + 1)
        dfs(0, [], 0)
        return rec
216. 组合总和 III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。 

来源:力扣(LeetCode)

解题

看题,该题跟第一题很像,三个条件:
1)多了一个 k 个数组合,结果都是k个数字
2)数组为【1~9】
3)组合中没有重复数
最终结果判断数量是否为k,大于k的剪枝
枚举1~9遍历,并且不能枚举上一个索引

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        # 枚举 + 剪枝
        rec = []
        def dfs(sumn, curqu, lenqu, idx):
            if lenqu == k and sumn == n:
                rec.append(curqu)
                return
            if lenqu > k or idx > 9 or sumn > n:
                return
            
            # 加和不加
            dfs(sumn + idx, curqu + [idx], lenqu + 1, idx + 1)
            dfs(sumn, curqu, lenqu, idx + 1)
        dfs(0,[],0, 1)
        return rec

以上我枚举用了两种方法,不考虑剪枝,比较一下这两种枚举方的复杂度,长度为 N
遍历法:N + N-1 + N-2 +……+ 2 + 1 – O(N²)
枚举:2+2+2+2+2 – O(2N)
也就是说后者性能更高。
第一题每次遍历都是全部,只能用遍历,第二题因为其特殊性,如果用后者无法去重,第三题因为不存在重复数据,每一次枚举我只要分加入数组和不加两种情况即可。

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target
的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

来源:力扣(LeetCode)

解题

这一题拿到手一脸懵逼,用前几题的思路写只能得到超时的结果,因为之前都是返回列表,而这个是返回数量。
之前动态规划就说过一句,一般没有必要的操作都是可以省略的,比如该题应该就是没有必要列举所有的列表的。之前刚写过一些动态规划,隐隐约约感觉可以用动态规划做。
先列一下各种情况吧 nums = [1,2,3], target = 4
1:
1
2:
1 1
2
3:
1 1 1
1 2
2 1
3
4:
1 1 1 1
1 1 2
1 2 1
1 3
2 1 1
2 2
3 1
似乎发现了规律,看4的各种情况,正好是 1, 2, 3 各种情况相加,再仔细看,1开头的正好是 3的所有情况,2开头的正好是2的所有情况,3开头的也正好是1的情况。
也就是说 dp[4] = dp[3] + dp[2] + dp[1]
而 dp[3] = dp[2] + dp[1] + 1
好奇怪,为什么有这种情况呢,就比如 3 ,我枚举了 1,然后所有 2 的所有情况拼上即可,枚举了2,所有1的情况拼上即可,枚举了3,就只有3,
我枚举的情况只能是给定的数组中的数a,然后补上对应的dp[i-a]所有情况即可,而如果是给定数组中的数,i + 0,只有本身的情况,似乎有眉目了。

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        # 动态规划 ,自己对着示例列5个就能发现规律了
        dp = [0] * (target + 1)
        minn = inf
        for num in nums:
            minn = min(minn, num)
            if num <= target:
                dp[num] = 1
        minn = min(minn, target)
        for i in range(minn,target+1):
            for num in nums:
                dp[i] += (dp[i-num] if i > num else 0)

        return dp[target]
20210611 回看组合总和

受背包问题的启发,过了一段时间回来再看这些问题,前三题因为需要枚举出所有的情况,所以是需要 回溯的办法的。一般这种题目如果是计算某个组合数或者组合数的最小个数,那就很轻易联想到背包问题,当时竟然还误打误撞做出来了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值