Skip to content
Open
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
177 changes: 177 additions & 0 deletions 39. Combination Sum.md
Original file line number Diff line number Diff line change
@@ -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[:]])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

, のあとにスペースを空けることをお勧めします。

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で二件しか引っかからなかったので外れてそう。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

スターリングの公式は物理の人は知っています。

なお、計算量は分割数による評価が結構いいやつであると思います。これは常識の外でしょう。
https://discord.com/channels/1084280443945353267/1251052599294296114/1280322673855041587

ああ、なるほど。常識の外という表現は「知らなくてもどうということはなく、知っていても褒められない」ということを表しているのですね。


## 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):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分なら range(target - candidate + 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)に仕事を渡すイメージ。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

同じイメージです。


同じ部署に仕事を渡すのと、違う部署に仕事を渡すというイメージでもあるかな。