問題
正の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 その
入力
candidates = [6, 3, 9]
target = 9出力
[[3, 3, 3], [3, 6], [9]]例 3
どう
入力
candidates = [4, 6]
target = 7出力
[]面接での聞かれ方
面接では、
- 書き始めて
少し すると、 「 [2, 3, 3]と[3, 2, 3]が両方出ていませんか?」と、 重なりを 指摘されます - 「同じ数は 1 回しか
使えないなら、 どこを 変えますか?」と、 条件を 変えた 続きを 聞かれます - 書き終えた
あとに 「どこで 探すのを やめていますか?」と、 枝の 切り方を 聞かれます
「次に
アルゴリズムのチートシートの「再帰と木」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは[2, 3, 3]、[3, 2, 3]、[3, 3, 2] のように、
段 2
並べ方のcandidates をj + 1 ではなく j にします。
段 3
選ぶ、path にpath からpath の
段 4
小さい
解答
Python
Python
def combination_sum(
candidates, target
):
a = sorted(candidates)
n = len(a)
out = []
path = []
def dfs(i, left):
if left == 0:
out.append(path[:])
return
for j in range(i, n):
if a[j] > left:
break
path.append(a[j])
dfs(j, left - a[j])
path.pop()
dfs(0, target)
return outJavaScript
JavaScript
function combinationSum(
candidates, target
) {
const a = [...candidates];
a.sort((p, q) => p - q);
const out = [];
dfs(a, 0, target, [], out);
return out;
}
function dfs(
a, i, left, path, out
) {
if (left === 0) {
out.push([...path]);
return;
}
const n = a.length;
for (let j = i; j < n; j++) {
const x = a[j];
if (x > left) {
break;
}
path.push(x);
const rest = left - x;
dfs(a, j, rest, path, out);
path.pop();
}
}Go
Go
import "sort"
func combinationSum(
candidates []int, target int,
) [][]int {
a := []int{}
a = append(a, candidates...)
sort.Ints(a)
n := len(a)
out := [][]int{}
path := []int{}
var dfs func(i, left int)
dfs = func(i, left int) {
if left == 0 {
p := []int{}
p = append(p, path...)
out = append(out, p)
return
}
for j := i; j < n; j++ {
if a[j] > left {
break
}
path = append(path, a[j])
dfs(j, left-a[j])
path = path[:len(path)-1]
}
}
dfs(0, target)
return out
}path は 1 本をpath その
計算量
- 時間は、
candidatesの長さを n、 奥行きの 最大を d ( targetをいちばん 小さい 数で 割った数。 この 問題では 最大 20 段) と すると、 最悪で O(nᵈ) ほどの 枝を たどります。 各段で 最大 n 通りを 試すからです。 実際は、 残りの 額を 超えた所で 打ち切るので、 ずっと 少なく済みます - 使う
記憶は、 返す 答えを 除くと O(d) です。 pathと呼び出しの 深さの 分です。 ほかに、 並べ替えた 写しに O(n) を使います - 答えの
数その ものが 多くなりうる 問題なので、 「答えを 1 つずつ 作る 手間に 比べて、 むだな 枝を どれだけ 切れるか」で 速さが 決まります
よく ある間違い
- 次の
始まりを 毎回 0 にする。 すべての 数から 選び直すと、 [2, 3, 3]と[3, 2, 3]のように同じ 組み合わせが 並べ方の 数だけ出ます。 次は jから選びます - 次の
始まりを j + 1にする。 同じ数を 2 回使えなくなり、例 1 の [2, 2, 2, 2]が出なくなります。 j + 1は「1 回ずつしか 使えない」問題の 書き方です pathの写しを 作らずに 答えへ入れる。 答えの 中の どれもが 同じ 1 本の pathを指し、 最後には 全部が 空の 並びに なります。 Python は path[:]、JavaScript は [...path]、Go は 新しい slice へ写します
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「残りの
額から 数を 引いていく バックトラックで 書きます。 並べ替えて おき、 次に 選ぶ数は いま選んだ 数から 右だけに すると、 同じ 組み合わせが 並べ方違いで 出ません。 同じ数を 何度も 使えるので、 次の 始まりは 自分の 位置です。 残りより 大きい数に 当たったら、 その先は 打ち切ります。」
書き終えたら、
「例 1 は
並べ替えると [2, 3, 5]で、残りは 8 です。 2 を 4 回選ぶと 0 に なり、 [2, 2, 2, 2]が入ります。 2 を 3 回の あとは 残り 2 で、 3 と 5 は 大きすぎるので 戻ります。 2 の あと 3、 3 で [2, 3, 3]、最初に 3 を 選ぶと 3 の あと 残り 2 で 行き止まり、 5 で [3, 5]です。最初に 5 を 選ぶと 残り 3 で、 5 から 右には 合う数が ありません。」
