0494 Target Sum#

Problem#

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 + before 2 and a - before 1 and concatenate them to build the expression +2-1. Return the number of different expressions that you can build, which evaluates to target.

Examples#

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

Constraint#

1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 1000

Analysis#

This is a complete knapsack problem, which can be solved using

  • backtracking

    • duplicated subproblems

  • dynamic programming

Solution#

Backtrack#

Backtrack generates a complete search tree, which leads to the time complexity of \(O(2^{len(nums)})\).

# use backtracking to solve the complete knapsack problem
def targetSum(nums, target):
    # define a result
    res = 0

    # define a backtrack function that returns the number of ways to assign symbols to make sum of nums equal to target for given first i elements and a current sum
    def backtrack(i, curr_sum):
        nonlocal res
        # base case: if we have reached the end of the nums
        # return +1 if the curr_sum is equal to target, skip otherwise
        if i == len(nums):
            res += 1 if curr_sum == target else 0
            return

        # backtrack(path, choices)
        choices = [-nums[i], nums[i]]
        for choice in choices:
            # make a choice
            curr_sum += choice
            # backtrack
            backtrack(i+1, curr_sum)
            # undo the choice
            curr_sum -= choice 
    
    backtrack(0, 0)
    return res

# test
# 5
nums = [1, 1, 1, 1, 1]
target = 3
print(targetSum(nums, target))

# 1
nums = [1]
target = 1
print(targetSum(nums, target))
5
1

Dynamic Programming#

# dp function: 
def targetSum(nums, target):

    # dp function: dp[i][j] represents the number of ways to assign symbols to make sum of nums equal to j for given first i elements
    # i = 0 means no element
    # i = 1 means the first element
    def dp(i, remain):
        # base case
        if i == 0:
            return 1 if remain == 0 else 0

        return dp(i-1, remain-nums[i-1]) + dp(i-1, remain+nums[i-1])
    
    return dp(len(nums), target)

# test
# 5
nums = [1, 1, 1, 1, 1]
target = 3
print(targetSum(nums, target))

# 1
nums = [1]
target = 1
print(targetSum(nums, target))
5
1
# DP Function with memo
def targetSum(nums, target):
    memo = {}

    def dp(i, remain):
        # base
        if i == 0:
            return 1 if remain == 0 else 0

        # if in memo
        if (i, remain) in memo:
            return memo[(i, remain)]

        # if not, then update memo: "+nums[i-1]" and "-nums[i-1]"
        memo[(i, remain)] = dp(i-1, remain - nums[i-1]) + dp(i-1, remain + nums[i-1])

        return memo[(i, remain)]
    
    return dp(len(nums), target)

# test
# 5
nums = [1, 1, 1, 1, 1]
target = 3
print(targetSum(nums, target))

# 1
nums = [1]
target = 1
print(targetSum(nums, target))
5
1