バックトラックで 同じ値を 含む 並びから 重ならない 部分集合を 全部集める
同じ
海外で定番の問題リスト 4 本には入っていません
問題
お店のnums を
- 値段の
組が 同じ物は 1 回だけ 入れます。 たとえば nums = [3, 3]で 1 つ目の 3 だけ選んだ組と 2 つ目の 3 だけ 選んだ組は、 どちらも [3]で同じ組です - 組の
中の 値段は、 小さい 順に並べます - 組どうしの
順は 辞書の 順です。 前の 値段から 比べ、 小さい方を 先にします。 片方が もう 片方の 頭と 同じなら、 短い方を 先にします。 たとえば []、[1]、[1, 2]、[2]の順です numsの長さは 0 以上 10 以下、 値段は どれも 1 以上 100 以下の整数です
入力と出力の例
例 1
5 が 2 個あります。[5] の 1 つだけです。
例 2
値段が
例 3
同じ
面接での聞かれ方
面接では、
- 「重複を
含む配列から、 重複しない 部分集合を 全部返してください」 - 「集合 (set) に
入れて 重なりを 消す 書き方」を 出すと、 「それだと、 作ってから 捨てる 分が 無駄に なりませんか? 作る 前に 防げますか?」と 聞かれます - 重複の
無い 版 (部 分集合を 全部 ) を 解いた あとに、 続けて 出される ことが 多い問題です
組の
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「再帰と木」に、
関わる学び
再帰は、
まず 考える手順
段 1
重複が[4, 4, 4] では 8 通り
段 2
まず nums を
段 3
重なりが[2, 5, 5] の[5] から[5, 5] が
段 4
ここまでの
解答
Python
Python
def subsets_with_dups(nums):
a = sorted(nums)
n = len(a)
out = []
path = []
def walk(s):
out.append(path[:])
# この段で最後に選んだ値
prev = None
for i in range(s, n):
x = a[i]
if x == prev:
continue
prev = x
path.append(x)
walk(i + 1)
path.pop()
walk(0)
return outJavaScript
JavaScript
function subsetsWithDups(nums) {
const a = [...nums];
a.sort((p, q) => p - q);
const out = [];
walk(a, 0, [], out);
return out;
}
function walk(a, s, path, out) {
out.push([...path]);
// この段で最後に選んだ値
let prev = null;
const n = a.length;
for (let i = s; i < n; i++) {
const x = a[i];
if (x === prev) {
continue;
}
prev = x;
path.push(x);
walk(a, i + 1, path, out);
path.pop();
}
}Go
Go
import "sort"
func subsetsWithDups(
nums []int,
) [][]int {
a := []int{}
a = append(a, nums...)
sort.Ints(a)
n := len(a)
out := [][]int{}
path := []int{}
var walk func(s int)
walk = func(s int) {
p := []int{}
p = append(p, path...)
out = append(out, p)
// この段で最後に選んだ値
prev := 0
for i := s; i < n; i++ {
x := a[i]
if x == prev {
continue
}
prev = x
path = append(path, x)
walk(i + 1)
path = path[:len(path)-1]
}
}
walk(0)
return out
}JavaScript の sort は、[10, 9] が [10, 9] のままに(p, q) => p - q をprev の
計算量
- 時間は O(2^n × n) です。
組は 2^n 個までで、 1 つを 写すのに n まで かかります。 並べ替えの O(n log n) は これに 含まれます - 使う
記憶は、 答えを 除くと O(n) です。 再帰の 深さと、 ここまでの 組の 長さが n までです
よく ある間違い
- 並べ替えずに、
隣と 同じなら飛ばす。 同じ 値段が 離れていると、 飛ばす判定が 効きません。 [5, 2, 5]のままだと[5]が 2 回入ります - 「最後に選んだ値」を
段を またいで持つ。 1 つの 変数を 全部の 段で 使い回すと、 深い段で 2 つ目の 5 を 足す所でも 飛ばしてしまい、 [5, 5]が作れません。 覚えておく 値は、 段 (再帰の 1 回) ごとに 用意します - ここまでの
組を 写さずに 答えに入れる。 答えの 中の 組が 全部同じ 並びを 指し、 最後には 空の 組ばかりになります - 集合で
重なりを 消してから 並べ替える。 正しい 答えには なりますが、 作ってから 捨てる 分が 無駄で、 面接では 作る 前に 防ぐ 書き方を 求められます
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「まず
並べ替えて、 同じ値を 隣り合わせます。 次に 足す値を、いまの 位置から 後ろの 中から 選ぶ再帰にし、 選ぶたびに ここまでの 組を 答えに 足します。 同じ段では、 最後に 選んだ値と 同じ 値を 飛ばします。 これで 同じ組は 1 回しか 作りません。 時間は O(2^n × n) です。」
書き終えたら、
「例 1 を
並べ替えると 2、 5、 5 です。 空の 組を 足し、 頭の 段で 2 を 選んで [2]、 続けて [2, 5]、 [2, 5, 5] です。 [2] の 次の 段で 2 つ目の 5 は 1 つ前と 同じなので 飛ばします。 頭の 段に 戻って 5 を 選び [5]、 [5, 5] です。 頭の 段の 2 つ目の 5 も 飛ばすので、 6 組で 終わります。」