python - leetcode - 39. 组合总和【经典题解 - 回溯算法】

一.题目

组合总和
描述:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

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

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同 1 <= target <= 40

二. 解题思路分析

回朔法的思想:

回朔法的重要思想在于: 通过枚举法,对所有可能性进行遍历。 但是枚举的顺序是 一条路走到黑,发现黑之后,退一步,再向前尝试没走过的路。直到所有路都试过。因此回朔法可以简单的理解为: 走不通就退一步的方枚举法就叫回朔法。而这里回退点也叫做回朔点。

回朔关键点

通过分析发现,回朔法实现的三大技术关键点分别是:

  • 一条路走到黑
  • 回退一步
  • 另寻他路

关键点的实现

那么如何才能用代码实现上述三个关键点呢?

  • for 循环
  • 递归

解释如下

for循环的作用在于另寻他路: 你可以用for循环可以实现一个路径选择器的功能,该路径选择器可以逐个选择当前节点下的所有可能往下走下去的分支路径。
例如: 现在你走到了节点a,a就像个十字路口,你从上面来到达了a,可以继续向下走。若此时向下走的路有i条,那么你肯定要逐个的把这i条都试一遍才行。而for的作用就是可以让你逐个把所有向下的i个路径既不重复,也不缺失的都试一遍

递归可以实现一条路走到黑和回退一步:
一条路走到黑: 递归意味着继续向着for给出的路径向下走一步。 如果我们把递归放在for循环内部,那么for每一次的循环,都在给出一个路径之后,进入递归,也就继续向下走了。直到递归出口(走无可走)为止。 那么这就是一条路走到黑的实现方法。 递归从递归出口出来之后,就会实现回退一步。

因此for循环和递归配合可以实现回朔: 当递归从递归出口出来之后。上一层的for循环就会继续执行了。而for循环的继续执行就会给出当前节点下的下一条可行路径。而后递归调用,就顺着这条从未走过的路径又向下走一步。这就是回朔

说了这么多,回朔法的通常模板是什么呢? 递归和for又是如何配合的呢?

回朔代码模板

def backward():
    
    if (回朔点):# 这条路走到底的条件。也是递归出口
        保存该结果
        return   
    
    else:
        for route in all_route_set :  逐步选择当前节点下的所有可能route
            
            if 剪枝条件:
                剪枝前的操作
                return   #不继续往下走了,退回上层,换个路再走
            
            else#当前路径可能是条可行路径
            
                保存当前数据  #向下走之前要记住已经走过这个节点了。例如push当前节点
        
                self.backward() #递归发生,继续向下走一步了。
                
                回朔清理     # 该节点下的所有路径都走完了,清理堆栈,准备下一个递归。例如弹出当前节点

这里剪枝操作指的是: 对于有些问题,你走着走着,若某种情况发生了,你就已经直到不能继续往下走了,再走也没有用了。而这个情况就被称之为剪枝条件。

而DFS就是一个最典型的回朔法的应用。

三. 解法

回溯

class Solution(object):
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        # 解法一
        candidates = sorted(candidates)
        ans = []
        def find(s, use, remain):
            for i in range(s, len(candidates)):
                c = candidates[i]
                if c == remain:
                    ans.append(use + [c])
                if c < remain:
                    find(i, use + [c], remain - c)
                if c > remain:
                    return
        find(0, [], target)
        return ans

		# 解法二
        res = []
        def combination(candidates,target,res_list):
            if target < 0:
                return
            if target == 0:
                res.append(res_list)
            for i,c in enumerate(candidates):
                # 为了避免重复 (例如candiactes=[2,3,6,7],target=7,输出[[2,2,3],[3,2,2][7]])
                # 传到的下一个candicate为candicates[i:]
                combination(candidates[i:],target-c,res_list+[c])
        combination(candidates,target,[])
        return res

		# 解法三
        candidates.sort()
        n = len(candidates)
        res = []
        def backtrack(i, tmp_sum, tmp):
            if tmp_sum > target or i == n:
                return
            if tmp_sum == target:
                res.append(tmp)
                return
            for j in range(i, n):
                if tmp_sum + candidates[j] > target:
                    break
                backtrack(j, tmp_sum + candidates[j], tmp + [candidates[j]])
        backtrack(0, 0, [])
        return res

附上动态规划解法

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        dp = {
    
    i:[] for i in range(target+1)}

        # 这里一定要将candidates降序排列
        for i in sorted(candidates,reverse=True):
            for j in range(i,target+1):
                if j==i:
                    dp[j] = [[i]]
                else:
                    dp[j].extend([x+[i] for x in dp[j-i]])
        return dp[target]

猜你喜欢

转载自blog.csdn.net/qq_43030934/article/details/131639344