子集和问计数问题
问题:给定一组数字,能否选出一个子集,使其和等于目标值,如果能,请计算不同的方法数。如果不能,请返回 0.
第 1 步:定义状态
dp[i][w]表示使用数组前i个元素凑成和w的不同方法总数。
第 2 步:推导递推关系
计算 dp[i][w] 时,我们考虑第 i 个元素 nums[i-1]:
有两个选择:
选择 1:不选 nums[i-1]
- 方法数:
dp[i-1][w]
选择 2:选nums[i-1]
- 前提:
w >= nums[i-1] - 方法数:
dp[i-1][w - nums[i-1]]
合并后得到:
txt
dp[i][w] = dp[i-1][w]
+ (w >= nums[i-1] ? dp[i-1][w - nums[i-1]] : 0)
朴素递归解
java
public static int findSubsetSumWaysRecursion(int[] nums, int target) {
return findSubsetSumWaysRecursionHelper(nums, target, nums.length);
}
private static int findSubsetSumWaysRecursionHelper(int[] nums, int remainSum, int i) {
if (i == 0) {
if (remainSum == 0) {
return 1;
}else {
return 0;
}
}
int count = findSubsetSumWaysRecursionHelper(nums, remainSum, i - 1);
if (remainSum >= nums[i-1]) {
count += findSubsetSumWaysRecursionHelper(nums, remainSum - nums[i-1], i - 1);
}
return count;
}
记忆化
java
public static int findSubsetSumWaysMemo(int[] nums, int target) {
int n = nums.length;
int[][] memo = new int[n+1][target+1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= target; j++) {
memo[i][j] = -1;
}
}
return findSubsetSumWaysMemoHelper(nums, target, nums.length, memo);
}
private static int findSubsetSumWaysMemoHelper(int[] nums, int remainSum, int i, int[][] memo) {
if (i == 0) {
if (remainSum == 0) {
return 1;
}else {
return 0;
}
}
if (memo[i][remainSum] != -1) {
return memo[i][remainSum];
}
int count = findSubsetSumWaysMemoHelper(nums, remainSum, i - 1, memo);
if (remainSum >= nums[i-1]) {
count += findSubsetSumWaysMemoHelper(nums, remainSum - nums[i-1], i - 1, memo);
}
memo[i][remainSum] = count;
return count;
}
制表法
java
public static int findSubsetSumWaysTabulation(int[] nums, int target) {
int n = nums.length;
int[][] dp = new int[n+1][target+1];
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}
for (int j = 0; j <= target; j++) {
dp[0][j] = 0;
}
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= target; j++) {
dp[i][j] = dp[i-1][j];
if (j >= nums[i-1]) {
dp[i][j] += dp[i-1][j-nums[i-1]];
}
}
}
return dp[n][target];
}
空间优化法
public static int findSubsetSumWaysOptimized(int[] nums, int target) {
int n = nums.length;
int[] dp = new int[target+1];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = target; j >= nums[i-1]; j--) {
dp[j] += dp[j-nums[i-1]];
}
}
return dp[target];
}
应用
可应用子集和计数问题来求解目标和计数问题。
示例代码
java
public static int findTargetSumWays(int[] nums, int target) {
int sum = Arrays.stream(nums).sum();
int subTarget = (target + sum) / 2;
return SubsetSum.findSubsetSumWaysOptimized(nums, subTarget);
}