From ba64c8ba0e837b62842eb646d3e42f432f2252e6 Mon Sep 17 00:00:00 2001 From: nittoco <166355467+nittoco@users.noreply.github.com> Date: Wed, 10 Jul 2024 22:54:50 +0900 Subject: [PATCH] 39. Combination Sum https://leetcode.com/problems/combination-sum/description/ Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order. The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different. 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. --- 39. Combination Sum.md | 180 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 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..27c0626 --- /dev/null +++ b/39. Combination Sum.md @@ -0,0 +1,180 @@ +### Step1 + +- 再帰やstackでも解けそうだが、手でやるならcandidates配列をfor文で舐めるので、そのような書き方にしてみた +- 思ったよりややこしくなってしまった + - 変数名もいまいちな気もする + - ネストが深い気もするが、これ以上どうしようもないよなあ + - 「各配列の値について、今まで追加した要素についてそれぞれ、配列の値をできる限り追加」なのでややこしい + - コピーが結構発生してしまうが、これもしょうがない + - これだとキツくて、やっぱstackとか使った方が良かったか、、、 + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + all_nums_and_sum = [] + sum_is_target = [] + all_nums_and_sum.append(([], 0)) + for candidate in candidates: + added_nums_and_sum = [] + for current_nums, current_sum in all_nums_and_sum: + added_nums = current_nums.copy() + added_sum = current_sum + while True: + if added_sum + candidate > target: + break + if added_sum + candidate == target: + sum_is_target.append(added_nums + [candidate]) + break + added_nums += [candidate] + added_sum += candidate + added_nums_and_sum.append((added_nums.copy(), added_sum)) + all_nums_and_sum += added_nums_and_sum + return sum_is_target +``` + +### Step2 + +- Stackでの解法 + - 後で、stackに入れる方法をうまくやればwhile True: のネストはいらないことに気づく + - ただwhile Trueの方が手でやる操作としては素直な気もする + - まだ変数名はいまいちなきが(後で直す) + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + nums_index_sum_stack = [([], 0, 0)] + all_combinations = [] + while nums_index_sum_stack: + nums, index, current_sum = nums_index_sum_stack.pop() + if index >= len(candidates): + continue + while True: + if current_sum > target: + break + if current_sum == target: + all_combinations.append(nums.copy()) + break + nums_index_sum_stack.append((nums.copy(), index + 1, current_sum)) + nums.append(candidates[index]) + current_sum += candidates[index] + return all_combinations +``` + +- 再帰での解法 + - どうもネストが深くなる + - 今回はwhile Trueより,条件を横に置いた方がいいと思いこうした + - まだ変数名はいまいちな気がする(後で直す) + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + + # 現在の要素を含む、和がtarget以下の組み合わせと、その和を列挙 + def enumerate_sum_under_target_after_index(index): + all_nums_and_sum = [] + if index == len(candidates): + return [([], 0)] + for i in range(index + 1, len(candidates) + 1): + for current_nums, current_sum in enumerate_sum_under_target_after_index(i): + while current_sum + candidates[index] <= target: + current_nums.append(candidates[index]) + current_sum += candidates[index] + all_nums_and_sum.append((current_nums.copy(), current_sum)) + return all_nums_and_sum + + target_combinations = [] + for i in range(len(candidates)): + for current_nums, current_sum in enumerate_sum_under_target_after_index(i): + if current_sum == target: + target_combinations.append(current_nums) + return target_combinations + +``` + +https://github.com/Exzrgs/LeetCode/pull/13/files#diff-5ea0de015187769c7eee5e7ab623e2bc75c88a7c051921b35d5d5826aa0ebb26R35 + +https://github.com/SuperHotDogCat/coding-interview/pull/11/files + +- 結構みんなbacktrackで書いてるなあ(backtrackが個人的にしっくり来てないんけど、一応練習した方がいいのかな) +- stackや再帰での引き継ぎの選択肢として、自分のStep1では「今のindexをできるだけ入れて次のindexへ」とやったが + - 今の値を1個入れて今のindexに留まるものと、何も入れずに次のindexに行くものを用意 + - 今の値から最後の値まで1個ずつ入れたものをそれぞれ用意(これはちょっと不自然な気がした) +- 変数名に関する議論。_so_farというのは良いね。 +- 時間計算量、空間計算量 + - [これ](https://github.com/Mike0121/LeetCode/pull/1#discussion_r1578068513)によると、答えの数は分割数だが、(nums, index, sum)のsum List[List[int]]: + stack= [([], 0, 0)] + sum_is_target = [] + while stack: + nums_so_far, index, sum_so_far = stack.pop() + if index == len(candidates): + continue + if sum_so_far > target: + continue + if sum_so_far == target: + sum_is_target.append(nums_so_far) + continue + stack.append((nums_so_far, index + 1, sum_so_far)) + stack.append((nums_so_far + [candidates[index]], index, sum_so_far + candidates[index])) + return sum_is_target +``` + +backtrackも一応練習、上のstackを書いた後だとちょっとはしっくりくるかも + +```python + +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + combination_target_sum = [] + combination_searched = [] + + def make_combination(index, combination_sum): + if index == len(candidates): + return + if combination_sum > target: + return + if combination_sum == target: + combination_target_sum.append(combination_searched.copy()) + return + make_combination(index + 1, combination_sum) + combination_searched.append(candidates[index]) + make_combination(index, combination_sum + candidates[index]) + combination_searched.pop() + + make_combination(0, 0) + return combination_target_sum +``` + +### Step3 + +```python + +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + stack= [([], 0, 0)] + sum_is_target = [] + while stack: + nums_so_far, index, sum_so_far = stack.pop() + if index == len(candidates): + continue + if sum_so_far > target: + continue + if sum_so_far == target: + sum_is_target.append(nums_so_far) + continue + stack.append((nums_so_far, index + 1, sum_so_far)) + stack.append((nums_so_far + [candidates[index]], index, sum_so_far + candidates[index])) + return sum_is_target +```