力扣数组-3940组合总和-medium

题目

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

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

说明:

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

思路 回溯算法

以输入:candidates = [2, 3, 6, 7], target = 7 为例:

在这里插入图片描述
说明:

以 target = 7 为 根结点 ,创建一个分支的时 做减法 ;
每一个箭头表示:从父亲结点的数值减去边上的数值,得到孩子结点的数值。边的值就是题目中给出的 candidate 数组的每个元素的值;
减到 00 或者负数的时候停止,即:结点 00 和负数结点成为叶子结点;
所有从根结点到结点 00 的路径(只能从上往下,没有回路)就是题目要找的一个结果。
这棵树有 44 个叶子结点的值 00,对应的路径列表是 [[2, 2, 3], [2, 3, 2], [3, 2, 2], [7]],而示例中给出的输出只有 [[7], [2, 2, 3]]。即:题目中要求每一个符合要求的解是 不计算顺序 的。下面我们分析为什么会产生重复。

针对具体例子分析重复路径产生的原因(难点)
友情提示:这一部分我的描述是晦涩难懂的,建议大家先自己观察出现重复的原因,进而思考如何解决。

产生重复的原因是:在每一个结点,做减法,展开分支的时候,由于题目中说 每一个元素可以重复使用,我们考虑了 所有的 候选数,因此出现了重复的列表。

一种简单的去重方案是借助哈希表的天然去重的功能,但实际操作一下,就会发现并没有那么容易。

可不可以在搜索的时候就去重呢?答案是可以的。遇到这一类相同元素不计算顺序的问题,我们在搜索的时候就需要 按某种顺序搜索。具体的做法是:每一次搜索的时候设置 下一轮搜索的起点 begin,请看下图。
在这里插入图片描述

即:从每一层的第 22 个结点开始,都不能再搜索产生同一层结点已经使用过的 candidate 里的元素。

Python3 的 [1, 2] + [3] 语法生成了新的列表,一层一层传到根结点以后,直接 res.append(path) 就可以了;
基本类型变量在传参的时候,是复制,因此变量值的变化在参数里体现就行,所以 Python3 的代码看起来没有「回溯」这个步骤。

from typing import List


class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:

        def dfs(candidates, begin, size, path, res, target):
            if target < 0: # 不存在
                return
            if target == 0:
                res.append(path) # 更新路径
                return

            for index in range(begin, size):# 索引位置应该在开始的那个参数到数组长度范围之间
                dfs(candidates, index, size, path + [candidates[index]], res, target - candidates[index]) # 更新dfs,begin更新至新的index。path路径添加上一个index的值,target减去新的index。比如最后减完如果剩的是0,说明这个数组成立。 以此作为循环

        size = len(candidates) #确定size是数组的长度
        if size == 0:
            return []
        path = []
        res = []
        dfs(candidates, 0, size, path, res, target)
        return res

另一种思路

变量意义:use表示已经使用过的数(组成的列表),remain表示距离target还有多大。

对candidates升序排序,以方便根据remain的大小使用return减小搜索空间;
递归求可能的组合。具体的,每次递归时对所有candidates做一次遍历,情况有3:1,满足条件,则答案加入一条;2,不足,继续递归,3,超出,则直接退出本路线。
注意每层递归都对全体candidates做遍历会导致如[2,2,3],[3,2,2]这样的对称重复的答案的产生。这是因为发生了“往前选择”的情况,我们每次更深层的递归都往后缩小一个candidates,强制函数只能“往后选择”,这将不会出现重复答案。

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates = sorted(candidates) #先对数组排序

        ans = []

        def find(s, use, remain):
            for i in range(s, len(candidates)): # 定义循环范围在开始和数组长度之间
                c = candidates[i] # c是数组里的数值
                if c == remain: # 假如c的值等于相减后的剩余值
                    ans.append(use + [c]) #已经找到该组合
                if c < remain: #c小于剩余值,还能减。所以要对现在的范围进行更新。初始值由s变成i,use变成use+c,剩余值变成remain-c
                    find(i, use + [c], remain - c)
                if c > remain: #c大于剩余值,减不了了,放弃
                    return
        find(0, [], target) # 从0开始。剩余值初始值就是target。use过的值放进[]

        return ans

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值