Leetcode 39 조합 합
Source: Dev.to
위에 제공된 소스 링크 외에 번역할 텍스트가 없습니다. 번역을 원하는 본문을 제공해 주시면 한국어로 번역해 드리겠습니다.
Problem Statement
서로 다른 정수들로 이루어진 배열 candidates와 목표 정수 target이 주어지면, 선택한 숫자들의 합이 target이 되는 모든 고유 조합을 반환합니다.
각 후보를 무한히 사용할 수 있습니다. 두 조합은 최소 하나의 숫자 빈도가 다르면 서로 다른 것으로 간주합니다.
예시:
[2,3,3],[3,2,3], 그리고[3,3,2]는 동일한 고유 조합을 나타냅니다.
예시
입력
candidates = [2,3,5]
target = 8
출력
[[2,2,2,2], [2,3,3], [3,5]]
수학적 공식화
문제는 다음과 같은 비음수 정수 x, y, z를 찾는 것으로 표현할 수 있다.
2·x + 3·y + 5·z = 8
해결책
class Solution:
def combinationSum(self, candidates, target):
results = []
def backtrack(remain, comb, start):
if remain == 0:
# 유효한 조합을 찾음
results.append(list(comb)) # 얕은 복사
return
if remain < 0:
# 합이 초과되어 이 경로 탐색을 중단
return
for i in range(start, len(candidates)):
# 후보 선택
comb.append(candidates[i])
# 같은 시작 인덱스로 계속 탐색 (재사용 허용)
backtrack(remain - candidates[i], comb, i)
# 백트래킹
comb.pop()
backtrack(target, [], 0)
return results
일반적인 함정
| 함정 | 발생 원인 | 해결 방법 |
|---|---|---|
results.append(comb) | comb이라는 가변 리스트에 대한 참조를 추가합니다; 이후 수정이 저장된 결과를 손상시킵니다. | 얕은 복사본을 저장하려면 results.append(list(comb))를 사용합니다. |
Looping over all candidates each recursion: for i in range(len(candidates)) | 같은 조합의 순열이 허용됩니다(예: [2,3]와 [3,2]). | start 인덱스부터 반복합니다: for i in range(start, len(candidates)). |
Not handling remain < 0 | 합이 초과된 후 불필요한 재귀 호출이 발생합니다. | remain < 0이면 즉시 반환합니다. |
깊이‑우선 탐색 워크스루
예제(candidates = [2,3,5], target = 8)에 대한 재귀 트리는 아래와 같이 나타내며(각 노드는 i – 인덱스, v – 값, remain, start를 보여줍니다).
root (target: 8)
└── [i:0, v:2] remain: 6, start: 0
├── [i:0, v:2] remain: 4, start: 0
│ ├── [i:0, v:2] remain: 2, start: 0
│ │ ├── [i:0, v:2] remain: 0, start: 0 ── Success → [2,2,2,2]
│ │ ├── [i:1, v:3] remain: -1 ── Fail
│ │ └── [i:2, v:5] remain: -3 ── Fail
│ ├── [i:1, v:3] remain: 1, start: 1
│ │ ├── [i:1, v:3] remain: -2 ── Fail
│ │ └── [i:2, v:5] remain: -4 ── Fail
│ └── [i:2, v:5] remain: -1 ── Fail
├── [i:1, v:3] remain: 3, start: 1
│ ├── [i:1, v:3] remain: 0 ── Success → [2,3,3]
│ └── [i:2, v:5] remain: -2 ── Fail
└── [i:2, v:5] remain: 1, start: 2
└── [i:2, v:5] remain: -4 ── Fail
└── [i:1, v:3] remain: 5, start: 1
├── [i:1, v:3] remain: 2, start: 1
│ ├── [i:1, v:3] remain: -1 ── Fail
│ └── [i:2, v:5] remain: -3 ── Fail
└── [i:2, v:5] remain: 0 ── Success → [3,5]
└── [i:2, v:5] remain: 3, start: 2
└── [i:2, v:5] remain: -2 ── Fail
Source: …
참조 표
| 현재 경로 | 남은 | 시작 | i (인덱스) | 후보[i] | 결과 |
|---|---|---|---|---|---|
[2] | 6 | 0 | 0 | 2 | 계속 |
[2,2] | 4 | 0 | 0 | 2 | 계속 |
[2,2,2] | 2 | 0 | 0 | 2 | 계속 |
[2,2,2,2] | 0 | 0 | 0 | 2 | 성공 (8) |
[2,2,2,3] | -1 | 0 | 1 | 3 | 실패 |
[2,2,2,5] | -3 | 0 | 2 | 5 | 실패 |
[2,2,3] | 1 | 0 | 1 | 3 | 계속 |
[2,2,3,3] | -2 | 1 | 1 | 3 | 실패 |
[2,2,3,5] | -4 | 1 | 2 | 5 | 실패 |
[2,2,5] | -1 | 0 | 2 | 5 | 실패 |
[2,3] | 3 | 0 | 1 | 3 | 계속 |
[2,3,3] | 0 | 1 | 1 | 3 | 성공 (8) |
[2,3,5] | -2 | 1 | 2 | 5 | 실패 |
[2,5] | 1 | 0 | 2 | 5 | 계속 |
[2,5,5] | -4 | 2 | 2 | 5 | 실패 |
[3] | 5 | 1 | 1 | 3 | 계속 |
[3,3] | 2 | 1 | 1 | 3 | 계속 |
[3,3,3] | -1 | 1 | 1 | 3 | 실패 |
[3,3,5] | -3 | 1 | 2 | 5 | 실패 |
[3,5] | 0 | 1 | 2 | 5 | 성공 (8) |
[5] | 3 | 2 | 2 | 5 | 계속 |
[5,5] | -2 | 2 | 2 | 5 | 실패 |