python - leetcode - 39. 조합 합 [고전적인 문제 해결 - 역추적 알고리즘]

1. 제목

조합 합
설명:
반복되는 요소가 없는 정수 배열 후보와 대상 정수 대상이 주어지면 숫자의 합이 대상 숫자를 대상으로 할 수 있는 후보에서 다양한 조합을 모두 찾아 목록 형식으로 반환합니다. 이러한 조합은 어떤 순서로든 반환할 수 있습니다.

후보자 중 동일한 수를 제한 없이 반복해서 선택할 수 있습니다. 적어도 하나의 숫자 중 선택한 숫자가 다른 경우 두 가지 조합이 다릅니다.

주어진 입력에 대해 목표에 합산되는 다양한 조합의 수는 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 <= 후보. 길이 <= 30
  • 2 <= 후보자[i] <= 40
  • 후보의 모든 요소는 서로 구별됩니다. 1 <= target <= 40

2. 문제해결 아이디어 분석

역추적 아이디어:

역추적 방법의 중요한 아이디어는 열거를 통해 모든 가능성을 탐색하는 것입니다. 하지만 열거의 순서는 어두운 면까지 쭉 가는 것인데, 어두운 면을 발견한 후에는 한발 물러서서 아직 가보지 못한 길을 시도해 보세요. 모든 길을 시험해 볼 때까지. 그러므로 역추적 방법은 간단히 다음과 같이 이해될 수 있다. 한 걸음 물러날 수 없을 때 물러나는 열거 방식을 역추적 방법이라 한다. 여기서 되돌림점을 회고점이라고도 합니다.

핵심 사항 검토

분석을 통해 역추적 방법이 실현하는 세 가지 핵심 기술 사항은 다음과 같습니다.

  • 길은 어둠으로 이어진다
  • 한발 물러서다
  • 다른 방법을 찾아보세요

핵심 포인트의 실현

그렇다면 위의 세 가지 핵심 사항을 코드로 어떻게 달성할 수 있을까요?

  • for 루프
  • 재귀

다음과 같이 설명했다

for 루프의 기능은 다른 방법을 찾는 것입니다. for 루프를 사용하여 현재 노드 아래에서 가능한 모든 분기 경로를 하나씩 선택할 수 있는 경로 선택기의 기능을 구현할 수 있습니다.
예: 이제 교차로와 같은 노드 a에 도달했습니다. 위에서 a에 도달했으며 계속 아래로 내려갈 수 있습니다. 이때 내려갈 수 있는 방법이 있다면, 모든 방법을 하나씩 시도해야 합니다. 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는 역추적 방법의 가장 일반적인 응용 프로그램입니다.

3. 솔루션

역추적

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