|
| 1 | +class Solution: |
| 2 | + """ |
| 3 | + 시간 복잡도 (Time Complexity): |
| 4 | + - 이 문제는 백트래킹(DFS) 기반으로 모든 가능한 조합을 탐색합니다. |
| 5 | + - 최악의 경우 각 조합에서 한 숫자를 여러 번 사용할 수 있으므로, 트리의 깊이는 최대 target // min(candidates) |
| 6 | + - 각 깊이마다 최대 len(candidates)만큼 분기 가능 |
| 7 | + - 따라서 시간 복잡도는 지수적으로 증가: O(2^T), T = target |
| 8 | + (정확한 upper bound는 계산하기 어렵지만, 대략적으로는 O(2^T) 또는 O(k^T)로 볼 수 있음) |
| 9 | +
|
| 10 | + 공간 복잡도 (Space Complexity): |
| 11 | + - 재귀 호출의 최대 깊이: O(T), T = target (가장 작은 숫자만 반복해서 사용하는 경우) |
| 12 | + - 경로 저장용 리스트(nums): O(T) |
| 13 | + - 결과 저장용 리스트(output): 최악의 경우 모든 가능한 조합 저장 → O(number of valid combinations * 평균 길이) |
| 14 | + 최종 공간 복잡도: **O(T + R)**, |
| 15 | + T: 재귀 깊이 / R: 결과 조합 수가 클 경우 output이 차지하는 공간 |
| 16 | + |
| 17 | + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: |
| 18 | + output, nums = [], [] |
| 19 | +
|
| 20 | + def dfs(start, total): |
| 21 | + if total > target: |
| 22 | + return |
| 23 | + if total == target: |
| 24 | + output.append(nums[:]) |
| 25 | + |
| 26 | + for i in range(start, len(candidates)): |
| 27 | + nums.append(candidates[i]) |
| 28 | + dfs(i, total + candidates[i]) |
| 29 | + nums.pop() |
| 30 | + |
| 31 | + dfs(0, 0) |
| 32 | + return output |
| 33 | + """ |
| 34 | + """ |
| 35 | + 2. dp |
| 36 | + dp[i]는 숫자들을 더해서 합이 i가 되는 모든 조합들을 저장합니다. |
| 37 | + dp[num - candidate]에 있는 조합에 candidate를 추가하면 num을 만들 수 있으므로 확장합니다. |
| 38 | + 중복된 조합을 방지하기 위해 candidates를 바깥 루프에 둠 (즉, 같은 숫자를 계속 재사용하되, 이전 숫자부터 누적). |
| 39 | + |
| 40 | + 시간 복잡도 (Time Complexity): |
| 41 | + 바깥 루프: 후보 숫자만큼 → O(N) |
| 42 | + 안쪽 루프: target까지 반복 → O(T) |
| 43 | + 가장 안쪽 루프: dp[num - candidate]에 있는 조합들을 모두 순회 → O(A) (A는 조합 개수 및 길이에 비례) |
| 44 | + → 따라서 최악의 경우 O(N × T × A) |
| 45 | +
|
| 46 | + 공간 복잡도 (Space Complexity): |
| 47 | + dp 배열 크기: target + 1 → O(T) |
| 48 | + 각 dp[i]에 저장된 조합 리스트들의 개수와 길이 → O(A) |
| 49 | + 따라서 전체 공간은 O(T × A) |
| 50 | +
|
| 51 | + Example 2의 dp 출력: |
| 52 | +
|
| 53 | + [[[]], [], [[2]], [], [], [], [], [], []] |
| 54 | + [[[]], [], [[2]], [], [[2, 2]], [], [], [], []] |
| 55 | + [[[]], [], [[2]], [], [[2, 2]], [], [[2, 2, 2]], [], []] |
| 56 | + [[[]], [], [[2]], [], [[2, 2]], [], [[2, 2, 2]], [], [[2, 2, 2, 2]]] |
| 57 | + [[[]], [], [[2]], [[3]], [[2, 2]], [], [[2, 2, 2]], [], [[2, 2, 2, 2]]] |
| 58 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2]], [], [[2, 2, 2, 2]]] |
| 59 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2], [3, 3]], [], [[2, 2, 2, 2]]] |
| 60 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2], [3, 3]], [[2, 2, 3]], [[2, 2, 2, 2]]] |
| 61 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2], [3, 3]], [[2, 2, 3]], [[2, 2, 2, 2], [2, 3, 3]]] |
| 62 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3], [5]], [[2, 2, 2], [3, 3]], [[2, 2, 3]], [[2, 2, 2, 2], [2, 3, 3]]] |
| 63 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3], [5]], [[2, 2, 2], [3, 3]], [[2, 2, 3], [2, 5]], [[2, 2, 2, 2], [2, 3, 3]]] |
| 64 | + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3], [5]], [[2, 2, 2], [3, 3]], [[2, 2, 3], [2, 5]], [[2, 2, 2, 2], [2, 3, 3], [3, 5]]] |
| 65 | + """ |
| 66 | + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: |
| 67 | + dp = [[] for _ in range(target + 1)] |
| 68 | + dp[0] = [[]] |
| 69 | + |
| 70 | + for candidate in candidates: |
| 71 | + for num in range(candidate, target + 1): |
| 72 | + for combination in dp[num - candidate]: |
| 73 | + dp[num].append(combination + [candidate]) |
| 74 | + return dp[target] |
0 commit comments