部分和問題(動的計画法)
二次元DPテーブルで部分集合の存在を効率的に判定しよう
O(n×S)
時間計算量
O(n×S)
空間計算量
中級
難易度
2次元DP
手法
🔧 実行設定
配列:
[2, 3, 7, 8, 10]
ターゲット:
11
DPテーブルサイズ:
6 × 12 = 72 セル
✅ DPにより効率的に部分集合の存在を判定
📚 推奨値
🔍
アルゴリズムを実行してください
左側の設定パネルから配列とターゲットを設定し、「部分和判定実行」ボタンを押してください
💻 実装例(JavaScript)
function subsetSumDP(arr, target) {
const n = arr.length;
// DPテーブルを初期化
// dp[i][j] = 配列の最初のi個の要素で和jが作れるかどうか
const dp = Array(n + 1).fill(null).map(() =>
Array(target + 1).fill(false)
);
// ベースケース:空集合の和は0
for (let i = 0; i <= n; i++) {
dp[i][0] = true;
}
// DPテーブルを埋める
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= target; j++) {
// i番目の要素を含めない場合
dp[i][j] = dp[i - 1][j];
// i番目の要素を含める場合(可能であれば)
if (j >= arr[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j - arr[i - 1]];
}
}
}
return dp[n][target];
}
// 部分集合を復元する関数
function findSubset(arr, target) {
const n = arr.length;
const dp = Array(n + 1).fill(null).map(() =>
Array(target + 1).fill(false)
);
// DPテーブルを構築
for (let i = 0; i <= n; i++) {
dp[i][0] = true;
}
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= target; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= arr[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j - arr[i - 1]];
}
}
}
// 部分集合が存在しない場合
if (!dp[n][target]) {
return null;
}
// バックトラックで部分集合を復元
const subset = [];
let i = n, j = target;
while (i > 0 && j > 0) {
// 現在の値が上の行から来ていない場合、要素を含める
if (dp[i][j] !== dp[i - 1][j]) {
subset.push(arr[i - 1]);
j -= arr[i - 1];
}
i--;
}
return subset.reverse();
}
// 使用例
const array = [2, 3, 7, 8, 10];
const target = 11;
console.log(subsetSumDP(array, target)); // true
console.log(findSubset(array, target)); // [3, 8] または [1, 10] など
🎯 アルゴリズムの特徴
動的計画法のメリット
- • 指数時間の全探索を多項式時間に改善
- • 重複する部分問題を一度だけ解く
- • テーブルから部分集合の復元も可能
- • 理解しやすい二次元DP構造
実用的な応用例
- • ナップサック問題の基礎
- • 分割問題(配列を等しい和に分割)
- • 硬貨の組み合わせ問題
- • リソース配分の最適化
💡 ポイント: 部分和問題は動的計画法の入門として最適で、 より複雑なDP問題への基礎となります。