CN: Subset Sum Equal To K
#dp
Problem
Intuition
Time and Space Complexity
Memoization
O(N^K)
O(N^K) + O(N)
N^k
is the number of states. O(N) -> Recursion Stack
Tabulation
O(N^K)
O(N^K)
We store only the states computation. No recursion stack
Space Optimized Tabulation
O(N^K)
O(K+1)
Only the target possible states array is required
Base Cases understanding in Tabulation
// Base case: For target sum 0, there always exists an empty subset.
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
The first column of the table will always be set to true
because no matter what the value is in that given index, we can always choose not to pick up resulting in achieving the target sum.
And another base case is
// Base case: If the first element of arr is less than or equal to k,
//it is a valid subset.
if (arr[0] <= k) {
dp[0][arr[0]] = true;
}
When we check if the first element of the element is less than or equal to target sum, then it means the arr[0]
can be part of the subset with which we can achieve the target sum.
The reason we are especially checking for arr[0]
is we start exploring the states from index=1
Solution
import java.util.*;
import java.io.*;
public class Solution {
public static boolean subsetSumToK(int n, int k, int arr[]) {
// This function checks if there exists a subset of elements from arr that sums up to k.
// It uses a dynamic programming approach to solve the problem.
// Uncomment one of the following approaches to solve the problem:
// Approach 1: Recursive approach (commented out)
// return recursiveHelper(n - 1, k, arr);
// Approach 2: Memoization approach (commented out)
// Integer[][] dp = new Integer[n][k + 1];
// return memoizationHelper(n - 1, k, arr, dp);
// Approach 3: Tabulation approach
return tabulationHelper(n, k, arr);
}
private static boolean tabulationHelper(int n, int k, int[] arr) {
// Tabulation approach using a 2D boolean array to store intermediate results.
// dp[i][j] represents whether there exists a subset of elements from arr[0...i] that sums up to j.
boolean[][] dp = new boolean[n][k + 1];
// Base case: For target sum 0, there always exists an empty subset.
for (int i = 0; i < n; i++) {
dp[i][0] = true;
}
// Base case: If the first element of arr is less than or equal to k, it is a valid subset.
if (arr[0] <= k) {
dp[0][arr[0]] = true;
}
// Fill the dp table bottom-up
for (int idx = 1; idx < n; idx++) {
for (int target = 1; target <= k; target++) {
boolean notPick = dp[idx - 1][target];
boolean pick = false;
// Check if the current element arr[idx] can be picked
if (arr[idx] <= target) {
pick = dp[idx - 1][target - arr[idx]];
}
// Either pick or not pick the current element
dp[idx][target] = pick || notPick;
}
}
// The result is stored at the last cell of the dp table
return dp[n - 1][k];
}
private static boolean tabulationSpaceOptimizationHelper(int n, int k, int[] arr) {
// Tabulation approach with space optimization using two boolean arrays.
boolean[] prev = new boolean[k + 1]; // Array to store the previous row of the dp table.
prev[0] = true; // Base case: An empty subset can form a sum of 0.
if (arr[0] <= k) {
prev[arr[0]] = true; // Base case: If the first element of arr is less than or equal to k, it is a valid subset.
}
for (int idx = 1; idx < n; idx++) {
boolean[] curr = new boolean[k + 1]; // Array to store the current row of the dp table.
curr[0] = true; // Base case: An empty subset can form a sum of 0.
for (int target = 1; target <= k; target++) {
boolean notPick = prev[target];
boolean pick = false;
// Check if the current element arr[idx] can be picked
if (arr[idx] <= target) {
pick = prev[target - arr[idx]];
}
// Either pick or not pick the current element
curr[target] = pick || notPick;
}
prev = curr; // Update the previous row with the current row for the next iteration.
}
return prev[k]; // The result is stored at the last index of the prev array.
}
private static boolean memoizationHelper(int idx, int target, int[] arr, Integer[][] dp) {
// Memoization approach using a 2D Integer array to store intermediate results.
// dp[i][j] stores the result for the subproblem with arr[0...i] and target sum j.
if (target == 0) {
// If the target is down to zero it means we have found a subset whose
// sum is equal to target
return true;
}
if (idx == 0) {
// Checking if the first element is equal to target/remaining target, that
// way it target can be brought down to zero. Then we would know, if the
// first element of the array can form a subset which can be used to check
// the target
return arr[0] == target; // Base case: Only the first element is available.
}
if (dp[idx][target] != null) {
return dp[idx][target] == 1; // Return the memoized result if available.
}
boolean notPick = recursiveHelper(idx - 1, target, arr);
boolean pick = false;
// Check if the current element arr[idx] can be picked
if (arr[idx] <= target) {
pick = recursiveHelper(idx - 1, target - arr[idx], arr);
}
// Store the result in the dp table and return it
dp[idx][target] = pick || notPick ? 1 : 0;
return pick || notPick;
}
private static boolean recursiveHelper(int idx, int target, int[] arr) {
// Recursive approach to check if there exists a subset of elements from arr that sums up to the target.
if (target == 0) {
// If the target is down to zero it means we have found a subset whose
// sum is equal to target
return true;
}
if (idx == 0) {
// Checking if the first element is equal to target/remaining target, that
// way it target can be brought down to zero. Then we would know, if the
// first element of the array can form a subset which can be used to check
// the target
return arr[0] == target;
}
boolean notPick = recursiveHelper(idx - 1, target, arr);
boolean pick = false;
// Check if the current element arr[idx] can be picked
if (arr[idx] <= target) {
pick = recursiveHelper(idx - 1, target - arr[idx], arr);
}
// Either pick or not pick the current element
return pick || notPick;
}
}
Last updated
Was this helpful?