You are given an integer array nums and an integer target.
You want to build an expression out of nums by adding one of the symbols '+' and '-' before each integer in nums and then concatenate all the integers.
- For example, if
nums = [2, 1], you can add a'+'before2and a'-'before1and concatenate them to build the expression"+2-1".
Return the number of different expressions that you can build, which evaluates to target.
Example 1:
Input: nums = [1,1,1,1,1], target = 3 Output: 5 Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3. -1 + 1 + 1 + 1 + 1 = 3 +1 - 1 + 1 + 1 + 1 = 3 +1 + 1 - 1 + 1 + 1 = 3 +1 + 1 + 1 - 1 + 1 = 3 +1 + 1 + 1 + 1 - 1 = 3
Example 2:
Input: nums = [1], target = 1 Output: 1
Constraints:
1 <= nums.length <= 200 <= nums[i] <= 10000 <= sum(nums[i]) <= 1000-1000 <= target <= 1000
Approach 1: Brute Force
Algorithm
The brute force approach is based on recursion. We need to try to put both the + and - symbols at every location in the given nums array and find out the assignments which lead to the required result S.
For this, we make use of a recursive function calculate(nums, i, sum, S), which returns the assignments leading to the sum S, starting from the ith index onwards, provided the sum of elements up to the ith element is sum. This function appends a + sign and a - sign both to the element at the current index and calls itself with the updated sum as sum+nums[i] and sum−nums[i] respectively along with the updated current index as i+1. Whenever we reach the end of the array, we compare the sum obtained with S. If they are equal, we increment the count value to be returned.
Thus, the function call calculate(nums, 0, 0, S) returns the required number of assignments.
Implementation
public class Solution {
int count = 0;
public int findTargetSumWays(int[] nums, int S) {
calculate(nums, 0, 0, S);
return count;
}
public void calculate(int[] nums, int i, int sum, int S) {
if (i == nums.length) {
if (sum == S) {
count++;
}
} else {
calculate(nums, i + 1, sum + nums[i], S);
calculate(nums, i + 1, sum - nums[i], S);
}
}
}
Complexity Analysis
-
Time complexity: O(2n). Size of recursion tree will be 2n. n refers to the size of nums array.
-
Space complexity: O(n). The depth of the recursion tree can go up to n.
Approach 2: Recursion with Memoization
Algorithm
In the last approach, we can observe that a lot of redundant function calls were made with the same value of i as the current index and the same value of sum as the current sum, since the same values could be obtained through multiple paths in the recursion tree. In order to remove this redundancy, we make use of memoization as well to store the results which have been calculated earlier.
Thus, for every call to calculate(nums, i, sum, S), we store the result obtained in memo[i][sum+total], where total stands for the sum of all the elements from the input array. The factor of total has been added as an offset to the sum value to map all the sums
possible to positive integer range. By making use of memoization, we
can get the result of each redundant function call in constant time.
Implementation
public class Solution {
int total;
public int findTargetSumWays(int[] nums, int S) {
total = Arrays.stream(nums).sum();
int[][] memo = new int[nums.length][2 * total + 1];
for (int[] row : memo) {
Arrays.fill(row, Integer.MIN_VALUE);
}
return calculate(nums, 0, 0, S, memo);
}
public int calculate(int[] nums, int i, int sum, int S, int[][] memo) {
if (i == nums.length) {
if (sum == S) {
return 1;
} else {
return 0;
}
} else {
if (memo[i][sum + total] != Integer.MIN_VALUE) {
return memo[i][sum + total];
}
int add = calculate(nums, i + 1, sum + nums[i], S, memo);
int subtract = calculate(nums, i + 1, sum - nums[i], S, memo);
memo[i][sum + total] = add + subtract;
return memo[i][sum + total];
}
}
}
Complexity Analysis
-
Time complexity: O(t⋅n). The
memoarray of size O(t⋅n) has been filled just once. Here, t refers to the sum of the nums array and n refers to the length of the nums array. -
Space complexity: O(t⋅n). The depth of recursion tree can go up to n. The
memoarray contains t⋅n elements.
Approach 3: 2D Dynamic Programming
Algorithm
The idea behind this approach is as follows. Suppose we can find out the number of times a particular sum, say sumi is possible up to a particular index, say i, in the given nums array, which is given by say counti. Now, we can find out the number of times the sum sumi+nums[i] can occur easily as counti. Similarly, the number of times the sum sumi−nums[i] occurs is also given by counti.
Thus, if we know all the sums sumj's which are possible up to the jth index by using various assignments, along with the corresponding count of assignments, countj, leading to the same sum, we can determine all the sums possible up to the (j+1)th index along with the corresponding count of assignments leading to the new sums.
Based on this idea, we make use of a dp to determine the number of assignments which can lead to the given sum. dp[i][j] refers to the number of assignments which can lead to a sum of j up to the ith index. To determine the number of assignments which can lead to a sum of sum+nums[i] up to the (i+1)th index, we can use dp[i][sum+nums[i]]=dp[i][sum+nums[i]]+dp[i−1][sum]. Similarly, dp[i][sum−nums[i]]=dp[i][sum−nums[i]]+dp[i−1][sum]. We iterate over the dp array in a row-wise fashion, i.e., firstly, we obtain all the sums which are possible up to a particular index along with the corresponding count of assignments and then proceed for the next element (index) in the nums array.
But, since the sum can range from -total to total, where total equals to the sum of the nums array, we need to add an offset of total to the sum indices (column number) to map all the sums obtained to positive range only.
At the end, the value of dp[n−1][S+total] gives us the required number of assignments. Here, n refers to the number of elements in the nums array.
Implementation
public class Solution {
public int findTargetSumWays(int[] nums, int S) {
int total = Arrays.stream(nums).sum();
int[][] dp = new int[nums.length][2 * total + 1];
dp[0][nums[0] + total] = 1;
dp[0][-nums[0] + total] += 1;
for (int i = 1; i < nums.length; i++) {
for (int sum = -total; sum <= total; sum++) {
if (dp[i - 1][sum + total] > 0) {
dp[i][sum + nums[i] + total] += dp[i - 1][sum + total];
dp[i][sum - nums[i] + total] += dp[i - 1][sum + total];
}
}
}
return Math.abs(S) > total ? 0 : dp[nums.length - 1][S + total];
}
}
Complexity Analysis
-
Time complexity: O(t⋅n). The
dparray of size O(t⋅n) has been filled just once. Here, t refers to the sum of the nums array and n refers to the length of the nums array. -
Space complexity: O(t⋅n).
dparray of size t⋅n is used.
Approach 4: 1D Dynamic Programming
Algorithm
If we look closely at the last solution, we can observe that to evaluate the current row of dp, only the values of the last row of dp are needed. Thus, we can save some space by using a 1D DP array instead of a 2D DP array. The only change we need to make is that we have to create an array next of the same size as dp so that we can update it while scanning through dp since it is not safe to mutate dp when the iteration is in progress. After the iteration is completed, we set dp equal to next and create a new empty array next before the next iteration starts, and so on.
Implementation
public class Solution {
public int findTargetSumWays(int[] nums, int S) {
int total = Arrays.stream(nums).sum();
int[] dp = new int[2 * total + 1];
dp[nums[0] + total] = 1;
dp[-nums[0] + total] += 1;
for (int i = 1; i < nums.length; i++) {
int[] next = new int[2 * total + 1];
for (int sum = -total; sum <= total; sum++) {
if (dp[sum + total] > 0) {
next[sum + nums[i] + total] += dp[sum + total];
next[sum - nums[i] + total] += dp[sum + total];
}
}
dp = next;
}
return Math.abs(S) > total ? 0 : dp[S + total];
}
}
Complexity Analysis
-
Time complexity: O(t⋅n). Each of the n
dparrays of size t has been filled just once. Here, t refers to the sum of the nums array and n refers to the length of the nums array. -
Space complexity: O(t). Two
dparrays of size 2⋅t+1 are used, therefore the space usage is O(t).
Python Code will be added soon.