Keito

© 2024 Keito

技術ブログとポートフォリオ

部分和問題(動的計画法)

二次元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問題への基礎となります。