バックトラックで 1 つずつしか使えない数から足すと目標になる組を全部集める

同じ値が何度か出てくる数の並びから、それぞれの数を 1 回までしか使わずに、足すと目標になる組を重なりなく全部集めます。並べ替えて、同じ段で同じ値を 2 回選ばない練習です。

海外で定番の問題リスト 4 本には入っていません

問題

財布に入っている商品券の額面の並び candidates と、払いたい額 target を受け取ります。商品券は 1 枚ずつしか使えず、同じ額面の商品券が何枚かあることがあります。商品券を何枚か選んで、額面の合計がちょうど target になる額面の組を全部集めて、並びにして返す関数を書いてください。

  • 額面の組が同じ物は 1 回だけ入れます。たとえば額面 1 の券が 2 枚あるとき、どちらの 1 を使っても [1, 4] は 1 つの組です
  • 組の中の額面は、小さい順に並べます
  • 組どうしの順は辞書の順です。前の額面から比べ、小さい方を先にします
  • 組が 1 つも無ければ、空の並び [] を返します
  • candidates の長さは 1 以上 30 以下、額面はどれも 1 以上 50 以下の整数です。target は 1 以上 30 以下の整数です

入力と出力の例

例 1

並べ替えると 1、1、2、3、4 です。合計が 5 になるのは、1 と 1 と 3、1 と 4、2 と 3 の 3 組です。1 と 4 は、どちらの 1 を使っても 1 組と数えます。

candidates = [4, 1, 3, 1, 2] target = 5
[[1, 1, 3], [1, 4], [2, 3]]

例 2

6 の券が 3 枚ありますが、12 を作る組は [6, 6] の 1 つだけです。

candidates = [6, 6, 6] target = 12
[[6, 6]]

例 3

どの券も 3 より大きいので、3 は作れません。

candidates = [5, 10] target = 3
[]

面接での聞かれ方

面接では、短く次のように渡されることが多いです。

  • 「重複を含む数の配列と目標の値があります。各数を 1 回だけ使って、和が目標になる組み合わせを、重複なく全部返してください」
  • 同じ数を何回でも使える版 (組み合わせの和) のあとに、続けて出されることが多い問題です。「前の問題と、どこを変えましたか?」と聞かれます
  • 「重なりを集合で消さずに、作る前に防げますか?」と聞かれます

同じ数を何回使えるかと、同じ値の数が並びに何個あるかの両方を最初に確かめておくと、2 つの版を取り違えません。

ヒント

まだ開いていません

付箋は 4 枚。下の段ほど答えに近づきます

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

再帰は、同じ形の小さい問題に分けて自分を呼ぶ書き方です。木の深さや、全部の組み合わせを数える問題で使います。

main.py
def combination_sum_once(candidates: list[int], target: int) -> list[list[int]]:
    """
    Goal: 何を返すか

    idea1: まず思いつく案
    -> runtime: O(?)  space: O(?)

    idea2: もっと速い案
    -> runtime: O(?)  space: O(?)

    edge case: 空、1 つだけ、など
    """
    # ここに書きます
    pass


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

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