From bf8b1c6529ef7d208289cbc1c1db0fc6b2111e9b Mon Sep 17 00:00:00 2001 From: fuga-98 <134851906+fuga-98@users.noreply.github.com> Date: Fri, 23 May 2025 11:23:13 +0900 Subject: [PATCH] Create 39. Combination Sum.md https://leetcode.com/problems/combination-sum/description/ --- 39. Combination Sum.md | 177 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 39. Combination Sum.md diff --git a/39. Combination Sum.md b/39. Combination Sum.md new file mode 100644 index 0000000..55d941d --- /dev/null +++ b/39. Combination Sum.md @@ -0,0 +1,177 @@ +# **39. Combination Sum** + +# 進め方 + +Step1 : 問題を解く。 + +Step2 : 他の人のPRを参照し、コメントする。 + +Step3 : 3回続けてエラーが出ないように書く。ドキュメントを参照する。 + +# 実践 + +## Step1 + +### 思考ログ + +全部試せば良いのだろう。 + +The test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input. + +この制約なんだろう。 + +target / candidates[i] < = 20 で計算量はこいつが関係しそう。(target ≤ 40) + +計算量は、ちょっとわからない。 + +30mくらいかかった。一番外のループをnumにすることに気づくのが難しかった。 + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + result = [] + stack = [[0, []]] + for num in candidates: + next_stack = [] + while stack: + total, takeover_nums = stack.pop() + # next_stack に積んでいく + while total <= target: + if total == target: + result.append(takeover_nums[:]) + break + next_stack.append([total,takeover_nums[:]]) + total += num + takeover_nums += [num] + stack = next_stack + + return result +``` + +candidates = 2,3,4,5….とすると + +target / candidates をかけていけばよいので + +20 * 13 * 10 * 8 * …… = n ** n/ n! + +AIに聞いたら スターリング近似で**O は指数時間 (exponential time)** になります。 + +これは常識の範囲でしょうか。 + +Discordで二件しか引っかからなかったので外れてそう。 + +## Step2 + +### 同じ問題を解いた人のプルリクを見る + +https://github.com/olsen-blue/Arai60/pull/53 + +- 一回のループで終わらせている。 +- 2重ループはタプルでやれば一回のループに書き換えられそう +- 再帰で書いたほうが素直に発想ができる気がしました。 +- https://github.com/olsen-blue/Arai60/pull/53#discussion_r2021984357 + - 感情が乗っていて面白いですね。 + - 共感できたので、私の感情も育ってきているのかもしれないです。 +- DPでも解ける。coin changeの問題っぽいなと思いました。 + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sum_combinations = [[] for _ in range(target + 1)] + sum_combinations[0].append([]) + for candidate in candidates: # 縦: 使用アイテム + for sum_value in range(candidate, target + 1): # 横: sumの値 + for combination in sum_combinations[sum_value - candidate]: # 遷移元のリスト(値)をループ取り出し + sum_combinations[sum_value].append(combination + [candidate]) + print(sum_combinations) + return sum_combinations[target] + +``` + +- 3つ目のForが頭に入らなかったが、合計が一緒の組み合わせすべてに足す必要があるからか。 +- 数を数えるだけなら、こんな感じ。 + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sum_combinations = [0] * (target + 1) + sum_combinations[0] = 1 + for candidate in candidates: # 縦: 使用アイテム + for sum_value in range(candidate, target + 1): # 横: sumの値 + sum_combinations[sum_value] += sum_combinations[sum_value - candidate] + return sum_combinations[target] +``` + +DPだとメモリ使用量が多そうですね。 + +https://github.com/hroc135/leetcode/pull/49 + +- https://discord.com/channels/1084280443945353267/1233295449985650688/1242067855579545611 + - 計算量の話。常識には入らなそう。難しいと感じれていたのは良かったのではないでしょうか。 + - 計算量の見積もりは具体例でやればよいだろう。 + +## Step3 + +### 3回連続で再現 + +**DP** + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sum_combinations = [[] for _ in range(target + 1) ] + sum_combinations[0].append([]) + for candidate in candidates: + for total in range(candidate, target + 1): + for combo in sum_combinations[total - candidate]: + sum_combinations[total].append(combo + [candidate]) + return sum_combinations[-1] +``` + +何度も間違えた。あんまりしっくり来ていないのだと思う。 + +**再帰** + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + result = [] + def helper(index, total, combo): + if index >= len(candidates): + return + if total > target: + return + if total == target: + result.append(combo) + return + helper(index+1, total, combo) + added_total = total + candidates[index] + added_combo = combo + [candidates[index]] + helper(index, added_total, added_combo) + helper(0, 0, []) + return result +``` + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + def helper(index, total, combo): + if index >= len(candidates): + return [] + if total > target: + return [] + if total == target: + return [combo] + added_total = total + candidates[index] + added_combo = combo + [candidates[index]] + return helper(index + 1, total, combo) + helper(index, added_total, added_combo) + return helper(0, 0, []) +``` + +副作用のない再帰。最初はreturn Noneにしていたが、[]を返すと良い感じに。 + +こっちはほぼミスらない。 + +穴を下に掘る担当(added)と横に展開する担当(index+1)に仕事を渡すイメージ。 + +同じ部署に仕事を渡すのと、違う部署に仕事を渡すというイメージでもあるかな。