Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions 39.Combination-Sum/memo.md
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] の場合、これは分割数というものですね。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1がない2以上という意味です。

Copy link
Copy Markdown
Owner Author

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}乗ということは何となく覚えておこうと思います。


> 分割数の極限は、ハーディとラマヌジャンが求めています。
> a(n) ~ 1/(4nsqrt(3)) * e^(Pi * sqrt(2n/3)) as n -> infinity (Hardy and Ramanujan)
> https://oeis.org/A000041
> だいたい、13^sqrt(n) / 7n くらいですか。
22 changes: 22 additions & 0 deletions 39.Combination-Sum/sol1.py
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)]
24 changes: 24 additions & 0 deletions 39.Combination-Sum/sol2.py
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)
18 changes: 18 additions & 0 deletions 39.Combination-Sum/sol3.py
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
22 changes: 22 additions & 0 deletions 39.Combination-Sum/sol4.py
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):
Copy link
Copy Markdown

@olsen-blue olsen-blue Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seenは、visitedのような再訪阻止の変数を期待してしまう気がしました。(すでに見た(seen)場所というより、これからの注目場所のような気が、、、)
なので、index, from_indexみたいな感じとかどうでしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好みですが、currentはなくても良いかもしれません。今注目しているものは無印でも問題ないと思います。(sumだと、built-inと被るので、totalが好きです)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seenは、visitedのような再訪阻止の変数を期待してしまう気がしました

そうですね、他には start としている方が多かったです。from_indexを採用させていただきました。

currentはなくても良いかもしれません

この変数名にしたのは sum が built-in とかぶってしまうからだったのですが、total は良さそうです。
変数名としての 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
Copy link
Copy Markdown

@olsen-blue olsen-blue Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L.13のifブロックは魚拓をとって終了する処理なので、再帰関数の直下で行うのが好みですかね。
下記の機械の例が参考になるかもです。
https://discord.com/channels/1084280443945353267/1192728121644945439/1194203372115464272

再帰関数のメインの処理がどこかのかを、わかりやすく主張したい気持ちがあります。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、素直に読めるように書くべきですね。 if...else...で書き直してみました。

Copy link
Copy Markdown

@olsen-blue olsen-blue Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントの意図としてはこういうイメージでした。
returnで終わる終了条件を再帰関数の下に並べる+その後にメインのバックトラックの処理を書く、という感じです。

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_combinations

Copy link
Copy Markdown

@olsen-blue olsen-blue Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

まあ、今回で言うと next_sum のような、次の地点や担当者のことを考えるのは、BFSで有効(最初の到達で最短距離確定するため)だったり、枝刈りになったりしそうなので、考え方としては良いものなのではとは思っています。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The 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
23 changes: 23 additions & 0 deletions 39.Combination-Sum/sol4_revised.py
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
17 changes: 17 additions & 0 deletions 39.Combination-Sum/sol5.py
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