-
Notifications
You must be signed in to change notification settings - Fork 0
39. Combination Sum #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 くらいですか。 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seenは、visitedのような再訪阻止の変数を期待してしまう気がしました。(すでに見た(seen)場所というより、これからの注目場所のような気が、、、) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好みですが、currentはなくても良いかもしれません。今注目しているものは無印でも問題ないと思います。(sumだと、built-inと被るので、totalが好きです)
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
そうですね、他には start としている方が多かったです。from_indexを採用させていただきました。
この変数名にしたのは sum が built-in とかぶってしまうからだったのですが、total は良さそうです。 |
||
| 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 | ||
|
Comment on lines
+13
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. L.13のifブロックは魚拓をとって終了する処理なので、再帰関数の直下で行うのが好みですかね。 再帰関数のメインの処理がどこかのかを、わかりやすく主張したい気持ちがあります。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. なるほど、素直に読めるように書くべきですね。 if...else...で書き直してみました。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. コメントの意図としてはこういうイメージでした。 class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
all_combinations = []
def collect_combs(comb, index, total) -> None:
if total == target:
all_combinations.append(comb[:])
return
if total > target:
return
if index >= len(candidates):
return
collect_combs(comb, index + 1, total)
comb.append(candidates[index])
collect_combs(comb, index, total + candidates[index])
comb.pop()
collect_combs([], 0, 0)
return all_combinationsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. まあ、今回で言うと next_sum のような、次の地点や担当者のことを考えるのは、BFSで有効(最初の到達で最短距離確定するため)だったり、枝刈りになったりしそうなので、考え方としては良いものなのではとは思っています。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. なるほど、index + 1でも再帰を行えばfor文が不要になりますね。こちらも残しておきます。 |
||
| get_combination(i, combination, next_sum) | ||
| combination.pop() | ||
|
|
||
| get_combination(0, [], 0) | ||
| return combinations | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2がない場合はこれの差分になります。
https://discord.com/channels/1084280443945353267/1423147093488435240/1485506903717974157
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1がない2以上という意味です。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、この式だけなら理解できました。b[i+1] = a[i+1] - a[i]となるので、微分が現れるのですね。
あとは分割数が\sqrt{n}乗ということは何となく覚えておこうと思います。