377. Combination Sum IV - Explanation

Problem Link

Description

You are given an array of distinct integers nums and a target integer target, return the number of possible combinations that add up to target.

The test cases are generated so that the answer can fit in a 32-bit integer.

Example 1:

Input: nums = [3,1,2], target = 4

Output: 7

Explanation: The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

Example 2:

Input: nums = [1], target = 3

Output: 1

Constraints:

  • 1 <= nums.length <= 200
  • 1 <= nums[i], target <= 1000
  • All the elements of nums are unique.

Follow up: What if negative numbers are allowed in the given array? How does it change the problem? What limitation we need to add to the question to allow negative numbers?



Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Recursion - Trying every number at each step and recursively solving for the remaining sum
  • Dynamic Programming (Memoization) - Caching results for each target value to avoid recomputation
  • Dynamic Programming (Tabulation) - Building the count of ways from 0 up to the target
  • Permutations vs Combinations - Understanding that this problem counts permutations (order matters)

1. Recursion

Intuition

We need to count all possible combinations (with different orderings counted separately) that sum to the target. At each step, we can pick any number from the array and subtract it from our remaining sum. This naturally leads to a recursive approach where we try every number at each position.

Algorithm

  1. Sort the array to enable early termination when a number exceeds the remaining sum.
  2. Define a recursive function that takes the current remaining total.
  3. Base case: if total equals 0, we found a valid combination, return 1.
  4. For each number in the array, if it does not exceed the current total, recursively count combinations with the reduced total.
  5. Sum up all recursive results and return the count.
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        nums.sort()

        def dfs(total):
            if total == 0:
                return 1

            res = 0
            for i in range(len(nums)):
                if total < nums[i]:
                    break
                res += dfs(total - nums[i])
            return res

        return dfs(target)

Time & Space Complexity

  • Time complexity: O(nt)O(n ^ t)
  • Space complexity: O(t)O(t)

Where nn is the size of the array numsnums and tt is the given target.


2. Dynamic Programming (Top-Down)

Intuition

The recursive solution has overlapping subproblems since the same remaining total can be reached through different paths. By caching results for each total value, we avoid redundant computations. This memoization transforms exponential time complexity into polynomial.

Algorithm

  1. Sort the array and initialize a memoization map with base case: memo[0] = 1.
  2. Define a recursive function that checks the memo before computing.
  3. For each number that does not exceed the current total, add the result of the recursive call with the reduced total.
  4. Store the computed result in the memo before returning.
  5. Return the result for the target value.
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        nums.sort()
        memo = { 0 : 1 }

        def dfs(total):
            if total in memo:
                return memo[total]

            res = 0
            for num in nums:
                if total < num:
                    break
                res += dfs(total - num)
            memo[total] = res
            return res

        return dfs(target)

Time & Space Complexity

  • Time complexity: O(nt)O(n * t)
  • Space complexity: O(t)O(t)

Where nn is the size of the array numsnums and tt is the given target.


3. Dynamic Programming (Bottom-Up) - I

Intuition

Instead of working backwards from the target, we can build up the solution by computing the number of ways to reach each sum from 0 to target. For each sum, we check all numbers in the array and add the ways to reach the sum minus that number.

Algorithm

  1. Initialize a DP map with dp[0] = 1 (one way to make sum 0: use nothing).
  2. Iterate through all totals from 1 to target.
  3. For each total and each number in the array, if total minus the number exists in dp, add that count to dp[total].
  4. Return dp[target] as the final answer.
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = { 0 : 1 }

        for total in range(1, target + 1):
            dp[total] = 0
            for num in nums:
                dp[total] += dp.get(total - num, 0)

        return dp[target]

Time & Space Complexity

  • Time complexity: O(nt)O(n * t)
  • Space complexity: O(t)O(t)

Where nn is the size of the array numsnums and tt is the given target.


4. Dynamic Programming (Bottom-Up) - II

Intuition

We can also work backwards from the target. Starting at the target, each number represents how many ways we can reach the target from that sum. When we subtract a number from the current total, we propagate the count to the resulting sum.

Algorithm

  1. Sort the array and initialize dp[target] = 1.
  2. Iterate from target down to 1.
  3. For each total with a non-zero count, and for each number that does not exceed the total, add dp[total] to dp[total - num].
  4. Return dp[0], which accumulates all ways to reach sum 0 starting from target.
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        nums.sort()
        dp = defaultdict(int)
        dp[target] = 1
        for total in range(target, 0, -1):
            for num in nums:
                if total < num:
                    break
                dp[total - num] += dp[total]
        return dp[0]

Time & Space Complexity

  • Time complexity: O(nt)O(n * t)
  • Space complexity: O(t)O(t)

Where nn is the size of the array numsnums and tt is the given target.


Common Pitfalls

Confusing Combinations vs Permutations

This problem counts permutations (order matters), not combinations. [1,2,1] and [1,1,2] are counted separately. If you iterate over elements and track an index to avoid reusing earlier elements, you'll miss valid orderings.

# Wrong: treats [1,2,1] and [2,1,1] as the same
dfs(i + 1, total - nums[i])
# Correct: always start from index 0 to allow all orderings
for num in nums:
    res += dfs(total - num)

Missing Base Case for Zero Target

Forgetting to return 1 when total == 0. This base case represents finding one valid way to form the target. Returning 0 here causes all counts to be zero.

Not Handling Negative Intermediate Results

When using bottom-up DP with a hashmap, accessing dp[total - num] when total - num is negative or doesn't exist causes errors. Always check bounds or use get() with a default value.

# Wrong: KeyError if total - num < 0 or not in dp
dp[total] += dp[total - num]
# Correct: use default value
dp[total] += dp.get(total - num, 0)