C++で配列をK個の等しい合計値の部分集合に分割できるか判定する方法
問題概要
整数型の配列 nums と正整数 k が与えられます。この配列を、各部分集合の合計値がすべて等しくなるような k 個の空でない部分集合に分割できるかどうかを判定してください。
例えば、配列が [4,3,2,3,5,2,1]、k = 4 の場合を見てみましょう。この配列は [[5], [1,4], [2,3], [2,3]] のように4つの部分集合に分割でき、それぞれの合計はすべて 5 で等しくなります。したがって、答えは True となります。
解法のポイント:ビットマスクDP
この問題は、ビットマスク動的計画法(bitmask DP)によって効率的に解くことができます。n 個の要素に対して 2^n 通りの「使用済み要素の組み合わせ」を状態として持ち、各状態における累積合計を記録しながら探索を進めます。また、配列をあらかじめソートしておくことで、現在構築中のサブセットに入らない要素が出た時点で探索を打ち切れる点も重要な工夫です。
アルゴリズムの手順
- サイズ 2^n の2つのテーブル
dp(到達可能フラグ)とtotal(累積合計)を用意します。 - 配列
numsを昇順にソートし、全要素の合計をsumとします。 sum % kが 0 以外、またはソート後の最大要素がsum / kを超える場合は false を返します(この時点で分割不可能と確定するため)。dp[0] = trueとし、sum = sum / k(=各サブセットの目標合計値)を設定します。- i を 0 から 2^n − 1 まで走査します。
dp[i]が true の状態について、未使用の要素 j を追加できるかを試します。- 新しい状態
temp = i | (1 << j)を作成します。 nums[j] ≤ sum − (total[i] % sum)を満たす場合は要素を追加でき、dp[temp] = true、total[temp] = total[i] + nums[j]と更新します。- 条件を満たさない場合は、配列がソート済みであることからそれ以降のより大きな要素も収まらないため、内側のループを break します。
- 新しい状態
- 最終的に
dp[(2^n) − 1](全要素を使用した状態)が true であれば true を返します。
ここで total[i] % sum は「現在構築途中のサブセットの合計」を表します。次の要素を加えても目標値 sum を超えない場合のみ、その遷移が有効になります。
C++による実装例
以下の実装を見ると、仕組みがより理解しやすくなります。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int n = nums.size();
vector <bool> dp(1 << n);
vector <int> total(1 << n);
sort(nums.begin(), nums.end());
int sum = 0;
for(int i = 0; i < nums.size(); i++)sum += nums[i];
if(sum % k || nums[nums.size() - 1] > sum / k) return false;
dp[0] = true;
sum /= k;
for(int i = 0; i < (1 << n); i++){
if(dp[i]){
for(int j = 0; j < n; j++){
int temp = i | (1 << j);
if(temp != i){
if(nums[j] <= sum - (total[i] % sum)){
dp[temp] = true;
total[temp] = total[i] + nums[j];
}
else{
break;
}
}
}
}
}
return dp[(1 << n) - 1];
}
};
main(){
Solution ob;
vector<int> v = {4,3,2,3,5,2,1};
cout << (ob.canPartitionKSubsets(v, 4));
}
入力
[4,3,2,3,5,2,1] 4
出力
1
出力が 1(true)となり、この配列が合計値の等しい4つの部分集合に分割可能であることが確認できます。
計算量
- 時間計算量: O(2^n × n) — 状態数 2^n に対して、各状態から最大 n 個の遷移を調べます。
- 空間計算量: O(2^n) — dp テーブルと total テーブルの保存に必要です。
-
C++で集合をk個の部分集合に分割する方法の総数を動的計画法で求める
2つの数 e(要素数) と p(分割数) が与えられたとき、「集合の e 個の要素を p 個の部分集合(パーティション)に分割する方法が全部で何通りあるか」を求めるのがこの問題の目的です。 例1 入力 e=4 p=2 出力 Count of number of ways to partition a set into k subsets are: 7 説明 要素が a・b・c・d の4つである場合、これらを2つのグループに分ける方法は次の7通りあります。 (a)−(b,c,d)、(b)−(a,c,d)、(c)−(a,b,d)、(d)−(a,b,c)、(a,b)−(c,d)、(a,c)−(b,
-
C++で二分木を等しい合計値の2つの木に分割できるか判定する方法
問題概要n 個のノードを持つ二分木が与えられたとき、元の木からちょうど1本の辺を削除することで、その木を「ノード値の合計が等しい2つの木」に分割できるかどうかを判定するのがこの問題です。例えば、次のような入力が与えられたとします。この場合、出力は true になります。解法のアプローチこの問題は、各部分木の合計値を事前にすべて計算しておき、その中に「木全体の合計の半分」と一致する値が存在するかを確認することで解けます。手順は以下の通りです。部分木の合計値を格納するためのスタック st を用意します。solve() 関数を定義します。引数としてノードを受け取ります。ノードが null の場合は