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
180 changes: 180 additions & 0 deletions 39. Combination Sum.md
Original file line number Diff line number Diff line change
@@ -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 = []

Choose a reason for hiding this comment

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

(Step1のものにレビューするのも少しあれですが...)numsよりかはcombinationsでしょうか, 問題文や関数にあるものを命名に使うといい感じになる気がします。

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<kのペアの数(と合計した空間計算量)はどうなるかというのはちょっと考えたけどわからない

https://discord.com/channels/1084280443945353267/1200089668901937312/1202214255689203732

- この辺の議論を見る。再帰関数の引数としてremainingも入れる選択肢もあるが、[ここ](https://github.com/Mike0121/LeetCode/pull/1/files)には引数が少ない方がいいと書いてあったのでやめた
- targetを超えるまで押し込むときにwhile True: は気持ちとしてはそうだが嫌ですねと書かれていた^^; https://discord.com/channels/1084280443945353267/1200089668901937312/1202214255689203732
- ということで今までのを踏まえ、以下のように書く
- 変数名は、伝えたい中核メッセージを頭の中ではっきりさせてから決めると、いいかんじになるのかも
- 例えば、stackはstackであることが一番伝えたくて、numとindexとsumが入ってて〜というのはすぐ後を見ればまあわかる

```python

class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
stack= [([], 0, 0)]

Choose a reason for hiding this comment

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

細かいですが、stack= がくっついていますね。

sum_is_target = []
while stack:
nums_so_far, index, sum_so_far = stack.pop()

Choose a reason for hiding this comment

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

nums_so_farcombinationcombination_nums にすると、何の数字の集まりなのかがより明確になる気がします。

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)

Choose a reason for hiding this comment

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

組み合わせになり得るかの計算までに都度 複数回の再帰が必要で、動きを追いかけることが個人的に難しいと感じました。
この make_combination() は forループで書くこともできそうに思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

この make_combination() は forループで書くこともできそうに思いました。

これは再帰を使わないで書く、ということですか。いまいち方法が思いつかず、、、

Choose a reason for hiding this comment

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

ありがとうございます。

再帰を使わないで書く

はい、下記のようなイメージでした。

def make_combination(start, combination_sum):
    for index in range(start, len(candidates)):
        if combination_sum > target:
            return
        if combination_sum == target:
            combination_target_sum.append(combination_searched.copy())
            return
        combination_searched.append(candidates[index])
        make_combination(index, combination_sum + candidates[index])
        combination_searched.pop()

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、理解しました。コードまでありがとうございます。
確かに2回再帰をやってappendしたりpopしたりの操作をするとわかりにくいかもですね。

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 = []

Choose a reason for hiding this comment

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

step2のall_combinationsの方が個人的に好みです。sum_is_targetだと僕は英語のSVC構文のように, 見えてしまいます。

while stack:
nums_so_far, index, sum_so_far = stack.pop()

Choose a reason for hiding this comment

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

これも細かくてすみませんが、=stack.pop() の間がスペース2つですね。

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
```