バックトラックで同じ値を含む並びから重ならない部分集合を全部集める

同じ値が何度か出てくる整数の並びから、選び方の違う部分集合を、同じ物を 2 回入れずに全部集めます。並べ替えてから「同じ段で同じ値を 2 回選ばない」と決めて重なりを防ぐ練習です。

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

問題

お店の棚に、値段の書いた品物が並んでいます。値段の並び nums を受け取ります。同じ値段の品物が何個かあることがあります。品物をいくつか選んだときの値段の組 (1 つも選ばない空の組も含む) を全部集めて、並びにして返す関数を書いてください。

  • 値段の組が同じ物は 1 回だけ入れます。たとえば nums = [3, 3] で 1 つ目の 3 だけ選んだ組と 2 つ目の 3 だけ選んだ組は、どちらも [3] で同じ組です
  • 組の中の値段は、小さい順に並べます
  • 組どうしの順は辞書の順です。前の値段から比べ、小さい方を先にします。片方がもう片方の頭と同じなら、短い方を先にします。たとえば []、[1]、[1, 2]、[2] の順です
  • nums の長さは 0 以上 10 以下、値段はどれも 1 以上 100 以下の整数です

入力と出力の例

例 1

5 が 2 個あります。5 を 1 個選ぶ組は、どちらの 5 を選んでも [5] の 1 つだけです。

nums = [5, 2, 5]
[[], [2], [2, 5], [2, 5, 5], [5], [5, 5]]

例 2

値段が全部違うと、2 の 3 乗で 8 組です。

nums = [3, 1, 2]
[[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]

例 3

同じ値段が 3 個なら、選ぶ数が 0 から 3 個の 4 組だけです。

nums = [4, 4, 4]
[[], [4], [4, 4], [4, 4, 4]]

面接での聞かれ方

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

  • 「重複を含む配列から、重複しない部分集合を全部返してください」
  • 「集合 (set) に入れて重なりを消す書き方」を出すと、「それだと、作ってから捨てる分が無駄になりませんか? 作る前に防げますか?」と聞かれます
  • 重複の無い版 (部分集合を全部) を解いたあとに、続けて出されることが多い問題です

組の中の順と、組どうしの順を最初に確かめておくと、答えが 1 つに決まり、例と比べやすくなります。

ヒント

まだ開いていません

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

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

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

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

main.py
def subsets_with_dups(nums: list[int]) -> list[list[int]]:
    """
    Goal: 何を返すか

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

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

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


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

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