diff --git a/Python3/39. Combination Sum.md b/Python3/39. Combination Sum.md new file mode 100644 index 0000000..02fec74 --- /dev/null +++ b/Python3/39. Combination Sum.md @@ -0,0 +1,110 @@ +## Step 1. Initial Solution + +- 再帰で候補を使っていき結果を一つのリストに追加していく + - target_sumに到達したらリストに追加 + - それ以外はtargetを削って次に託す + - 特に問題文に記載はないがcandidatesが昇順ならここでbreakしたい +- 同じ組み合わせを入れないようにしたい + - previous_target_indexで前に戻れないようにする + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + target_combinations = [] + + def combination_sum_helper(prefix: list[int], target: int, previous_index: int) -> None: + for i in range(previous_index, len(candidates)): + if candidates[i] < target: + combination_sum_helper(prefix + [candidates[i]], target - candidates[i], i) + elif candidates[i] == target: + target_combinations.append(prefix + [candidates[i]]) + + combination_sum_helper([], target, 0) + return target_combinations +``` + +### Complexity Analysis + +- 時間計算量: +- 空間計算量: + - 見積もりが難しいので一旦後回し + +## Step 2. Alternatives + +- https://github.com/tokuhirat/LeetCode/pull/52/files#diff-91af53ad1e7f521bd1b22fbd7e06ea4139f8e95a23cb1c16d71b784e1343e4dbR10-R23 + - 方針は似ていてヘルパー関数にはすでに出来ている組み合わせと今のインデックスを保持 + - targetは変えずに毎回sum(combination)している + - 個人的には毎回sumするよりはtargetを変えたい + - `generate_combination(index + 1, combination[:])` + - ここでlist[:]の意味がよく分かっていなかったことに気づいた + - 全体スライスはコピーを作るのに使えるということらしい +- https://github.com/tokuhirat/LeetCode/pull/52/files#diff-91af53ad1e7f521bd1b22fbd7e06ea4139f8e95a23cb1c16d71b784e1343e4dbR10-R23 + - `sum_to_combinations = [[] for _ in range(target + 1)]` を更新していく方法 +- https://github.com/olsen-blue/Arai60/pull/53/files#r2021976335 + - 個人的にはバックしていく方法が思いついていなかったがこれは確かにバックトラックと言える +- https://github.com/olsen-blue/Arai60/pull/53/files#diff-f084bff8e4dbd771bf8a202d43b499bc30bffb7c10d4c5ccd2102f021910fd19R71-R90 + - スタックでやる方法 + - 中身は再帰と同じ + - stackという変数名は使いたくない気がした + - 自分でも実装 + + ```python + class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + target_combinations = [] + subtarget_combination_sum_index = [([], 0, 0)] + while subtarget_combination_sum_index: + combination, combination_sum, candidate_index = subtarget_combination_sum_index.pop() + for i in range(candidate_index, len(candidates)): + new_combination = combination + [candidates[i]] + new_sum = combination_sum + candidates[i] + if new_sum == target: + target_combinations.append(new_combination) + elif new_sum < target: + subtarget_combination_sum_index.append((new_combination, new_sum, i)) + + return target_combinations + ``` + +- https://github.com/olsen-blue/Arai60/pull/53/files#r2020113460 + - DPに対するコメント + - 確かにメモリの観点から使えない枝を残しておかないようにしたいならDPは微妙 + - バックトラックはDFSで枝刈りしているのと同じ +- https://github.com/Yoshiki-Iwasa/Arai60/pull/57/files#r1741307179 + - 計算量の話 + - 上手く評価する方法はないまであるか? + - 自分のやり方だと(targetを超えない組み合わせの数)×(candidate数)として表せる + - この問題の解の個数を target Kを用いて F(K), candidate数 N とすると + + $$ + 計算量 = \Sigma_{i=2}^{K-1}F(i)*N + $$ + + - F(K)<計算量 かつ F(2) ≤ 1とすると + + $$ + 計算量 List[List[int]]: + target_combinations = [] + + def combination_sum_helper(prefix: list[int], target: int, candidate_index: int) -> None: + for i in range(candidate_index, len(candidates)): + combination = prefix + [candidates[i]] + if candidates[i] == target: + target_combinations.append(combination) + elif candidates[i] < target: + combination_sum_helper(combination, target - candidates[i], i) + + combination_sum_helper([], target, 0) + return target_combinations +```