バックトラックで足すと目標になる組み合わせを全部集める

何度でも​使える​数の​並びから、​足すと​目標に​なる​組み合わせを、​重ならないように​全部返します。​選ぶ、​先へ​進む、​取り消す、を​くり返すバックトラックで、​選び​始める​位置を​決めて​重なりを​防ぐ​練習です。

海外で定番の問題リスト 4 本のうち 4 本に入っています (Blind 75、NeetCode 150、Grind 75、LeetCode Top Interview 150)

問題

正の整数の並び candidates と、正の整数 target を受け取ります。candidates の数を何度でも使ってよいとして、足すと target になる組み合わせを全部返す関数を書いてください。

  • candidates の値はどれも違います。同じ数を 2 回以上使うのはかまいません
  • 使う数とその回数が同じなら、同じ組み合わせです。[2, 3, 3] と [3, 2, 3] は 1 回だけ返します
  • 組み合わせの中は小さい順に並べます。組み合わせどうしは辞書の順 (先頭の数から比べ、小さいほうが先。同じなら次の数で比べる) に並べます
  • そういう組み合わせが無いときは、空の並び [] を返します
  • candidates の長さは 1 以上 30 以下、値は 2 以上 40 以下、target は 1 以上 40 以下です

入力と出力の例

例 1

入力

candidates = [3, 5, 2]
target = 8

出力

[[2, 2, 2, 2],
 [2, 3, 3],
 [3, 5]]

例 2

target そのものが並びにあれば、1 つだけの組み合わせも答えです。

入力

candidates = [6, 3, 9]
target = 9

出力

[[3, 3, 3], [3, 6], [9]]

例 3

どう足しても 7 にならないときは、空の並びです。

入力

candidates = [4, 6]
target = 7

出力

[]

面接での聞かれ方

面接では、「金額をちょうど払える硬貨の組み合わせを全部挙げてください」のような場面で出されることが多いです。

  • 書き始めて少しすると、「[2, 3, 3] と [3, 2, 3] が両方出ていませんか?」と、重なりを指摘されます
  • 「同じ数は 1 回しか使えないなら、どこを変えますか?」と、条件を変えた続きを聞かれます
  • 書き終えたあとに「どこで探すのをやめていますか?」と、枝の切り方を聞かれます

「次に選ぶ数は、いま選んだ数より前には戻らない」と決めるだけで、重なりが出なくなることを言えるのが芯です。

アルゴリズムのチートシートの「再帰と木」に、​この​型の​要点を​表と​コードで​まとめています。

main.py
def combination_sum(candidates, target):
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
print(combination_sum([3, 5, 2], 8))

できているか「例で確かめる」を押すと、問題の例を全部この関数に渡して、通ったかをここに出します