0/1背包问题的另一种形式是数列目标和问题,其一般形式为给定一个整数数列和一个整数,要求确认是否可以从这个数列中选取若干个元素使它们相加之和等于所给定的整数。下面的分割等和子集就是其中的一个例子。
给定一个包含正整数的数组nums,判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
实例如下:
nums = {14,9,8,4,3,2}
答案是True,数组可以分割成 [14, 4, 2] 和 [9, 8, 3],两个子集的元素相加之和都等于20。
我们可以看到这其实就是一个数列目标和问题,所要求的目标和就是整个数列所有元素相加和的一半。
使用与0/1背包问题相同的思路,我们使用一个二维数组来存储各种可能的组合,该数组的第一维代表数列数组的下标,第二维代表从0到目标和之间的每个整数,数组中的元素则是布尔值,DP[m][n]为True代表数列前m个元素存在一个子集其相加之和等于n。当DP[m][n]为True时,我们可以推断对于前m+1个元素来说存在一个子集其相加之和等于n· 也存在一个子集其相加之和等于n + nums[m + 1],也就是说DP[m + 1][n]为Ture,DP[m + 1][n + nums[m + 1]]为Ture。
使用以上的规律,我们可以根据数列数组的下标从小到大,依次推进,当某个代表目标和的数组元素被设置为True时,该问题的答案就是True,否则当整个DP数组填充完毕之后,返回False。
Java代码如下
class BottomUp {
public boolean canPartition(int[] nums) {
int N = nums.length;
if (N == 1) {
return false;
}
int sum = 0;
for (int i=0; i<N; i++) {
sum += nums[i];
}
if ((sum % 2) != 0) {
return false;
}
sum /= 2;
boolean[][] dp = new boolean[N + 1][sum + 1];
dp[0][0] = true;
for(int i=0; i < N; i ++) {
for(int j = 0; j <= sum; j ++) {
if (dp[i][j]) {
dp[i + 1][j] = true;
int k = j + nums[i];
if (k <= sum) {
dp[i + 1][k] = true;
}
}
}
if (dp[i + 1][sum] == true) {
return true;
}
}
return false;
}
}
要注意的是上面代码中的DP[0][0]是第一个设置为True的元素,其逻辑在于对于0个数列元素来所,其相加以后等于0。