diff --git a/39.Combination-Sum/memo.md b/39.Combination-Sum/memo.md new file mode 100644 index 0000000..2024685 --- /dev/null +++ b/39.Combination-Sum/memo.md @@ -0,0 +1,39 @@ +# 39. Combination Sum + +https://leetcode.com/problems/combination-sum/description/ + +nonuniqueな解を全て列挙してtupleにしてからuniqueにする回答方針: sol1.py + +入れる順番を前からに固定すれば uniqueになる: sol2.py + +DFSでも解ける: sol3.py +このバックトラック続きの問題の中になかったら思いついていないと思う + +関数にしつつバックトラックがわかりやすいようにした: sol4.py + +計算量がわからない +時間計算量: O((target/min(candidates))^{n}) +空間計算量: O(target/min(candidates)) + + +### コメント集 + +> https://discord.com/channels/1084280443945353267/1235829049511903273/1265717341178822787 + +https://github.com/fhiyo/leetcode/pull/52/changes + +yieldを用いた解法 + +https://discord.com/channels/1084280443945353267/1233295449985650688/1242118169968115845 +計算量の見積もり + +https://discord.com/channels/1084280443945353267/1233295449985650688/1242103186009886862 +分割数 +https://github.com/Mike0121/LeetCode/pull/1#discussion_r1578212926 + +> 答えの数ですが、candidates = [1..target] の場合、これは分割数というものですね。 + +> 分割数の極限は、ハーディとラマヌジャンが求めています。 +> a(n) ~ 1/(4nsqrt(3)) * e^(Pi * sqrt(2n/3)) as n -> infinity (Hardy and Ramanujan) +> https://oeis.org/A000041 +> だいたい、13^sqrt(n) / 7n くらいですか。 diff --git a/39.Combination-Sum/sol1.py b/39.Combination-Sum/sol1.py new file mode 100644 index 0000000..d9f3447 --- /dev/null +++ b/39.Combination-Sum/sol1.py @@ -0,0 +1,22 @@ +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sorted_candidates = sorted(candidates) + + def combination_sum_partial(partial_target: int): + assert partial_target >= 0 + if partial_target == 0: + return [[]] + result = [] + for num in sorted_candidates: + if num > partial_target: + break + combination_with_num = combination_sum_partial(partial_target - num) + result.extend( + [[num] + combination for combination in combination_with_num] + ) + + return result + + combos = combination_sum_partial(target) + unique = {tuple(sorted(c)) for c in combos} + return [list(t) for t in sorted(unique)] diff --git a/39.Combination-Sum/sol2.py b/39.Combination-Sum/sol2.py new file mode 100644 index 0000000..4dad350 --- /dev/null +++ b/39.Combination-Sum/sol2.py @@ -0,0 +1,24 @@ +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sorted_candidates = sorted(candidates) + + def combination_sum_partial(seen: int, partial_target: int): + assert partial_target >= 0 + if partial_target == 0: + return [[]] + result = [] + for i in range(seen, len(sorted_candidates)): + if sorted_candidates[i] > partial_target: + break + combination_with_num = combination_sum_partial( + i, partial_target - sorted_candidates[i] + ) + result.extend( + [ + [sorted_candidates[i]] + combination + for combination in combination_with_num + ] + ) + return result + + return combination_sum_partial(0, target) diff --git a/39.Combination-Sum/sol3.py b/39.Combination-Sum/sol3.py new file mode 100644 index 0000000..a104205 --- /dev/null +++ b/39.Combination-Sum/sol3.py @@ -0,0 +1,18 @@ +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sorted_candidates = sorted(candidates) + all_results = [] + frontier = [(0, 0, [])] + + while frontier: + current_sum, seen, elements = frontier.pop() + for i in range(seen, len(sorted_candidates)): + next_sum = current_sum + sorted_candidates[i] + if next_sum == target: + all_results.append(elements + [sorted_candidates[i]]) + break + if next_sum > target: + break + frontier.append((next_sum, i, elements + [sorted_candidates[i]])) + + return all_results diff --git a/39.Combination-Sum/sol4.py b/39.Combination-Sum/sol4.py new file mode 100644 index 0000000..cc14772 --- /dev/null +++ b/39.Combination-Sum/sol4.py @@ -0,0 +1,22 @@ +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sorted_candidates = sorted(candidates) + combinations = [] + + def get_combination(seen, combination, current_sum): + if current_sum == target: + combinations.append(combination.copy()) + return + for i in range(seen, len(sorted_candidates)): + combination.append(sorted_candidates[i]) + next_sum = current_sum + sorted_candidates[i] + if next_sum >= target: + if next_sum == target: + combinations.append(combination.copy()) + combination.pop() + return + get_combination(i, combination, next_sum) + combination.pop() + + get_combination(0, [], 0) + return combinations diff --git a/39.Combination-Sum/sol4_revised.py b/39.Combination-Sum/sol4_revised.py new file mode 100644 index 0000000..1381d3b --- /dev/null +++ b/39.Combination-Sum/sol4_revised.py @@ -0,0 +1,23 @@ +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + sorted_candidates = sorted(candidates) + combinations = [] + + def get_combination(from_index, combination, total): + if total == target: + combinations.append(combination.copy()) + return + for i in range(from_index, len(sorted_candidates)): + combination.append(sorted_candidates[i]) + next_total = total + sorted_candidates[i] + if next_total < target: + get_combination(i, combination, next_total) + else: + if next_total == target: + combinations.append(combination.copy()) + combination.pop() + return + combination.pop() + + get_combination(0, [], 0) + return combinations diff --git a/39.Combination-Sum/sol5.py b/39.Combination-Sum/sol5.py new file mode 100644 index 0000000..faeeb61 --- /dev/null +++ b/39.Combination-Sum/sol5.py @@ -0,0 +1,17 @@ +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + combinations = [] + + def get_combination(from_index, combination, total): + if total == target: + combinations.append(combination.copy()) + return + if total > target or from_index >= len(candidates): + return + get_combination(from_index + 1, combination, total) + combination.append(candidates[from_index]) + get_combination(from_index, combination, total + candidates[from_index]) + combination.pop() + + get_combination(0, [], 0) + return combinations