Facebook Pixel

377. Combination Sum IV

Problem Description

You are given an array of distinct integers nums and a target integer target. Your task is to find the number of possible combinations from the array that add up to the target.

Key points to understand:

  • You can use the same number from nums multiple times in a combination
  • The order of numbers matters (different orderings count as different combinations)
  • All integers in nums are distinct (no duplicates)
  • The answer is guaranteed to fit in a 32-bit integer

For example, if nums = [1, 2, 3] and target = 4, the possible combinations are:

  • 1 + 1 + 1 + 1
  • 1 + 1 + 2
  • 1 + 2 + 1
  • 2 + 1 + 1
  • 2 + 2
  • 1 + 3
  • 3 + 1

Notice that [1, 2, 1] and [2, 1, 1] are counted as different combinations because the order is different.

The solution uses dynamic programming where f[i] represents the number of ways to form sum i. Starting with f[0] = 1 (one way to make 0: use nothing), for each value from 1 to target, we check all numbers in nums. If a number x is less than or equal to the current sum i, we add f[i - x] to f[i], representing all the ways to form i - x plus the current number x.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about building up to the target sum step by step. If we want to find how many ways to reach a sum of target, we can break it down into smaller subproblems.

Consider this: to reach a sum of i, we could have previously reached some smaller sum and then added one of the numbers from our array. For instance, if we want to make 5 and we have the number 2 in our array, one way is to first make 3 (in however many ways possible) and then add 2.

This leads us to a key insight: the number of ways to make sum i equals the total of:

  • Number of ways to make i - nums[0] (then add nums[0])
  • Number of ways to make i - nums[1] (then add nums[1])
  • Number of ways to make i - nums[2] (then add nums[2])
  • ... and so on for each number in the array

This is a classic dynamic programming pattern where we build solutions to larger problems from solutions to smaller ones. We start from the base case: there's exactly one way to make a sum of 0 (by choosing nothing).

For each sum from 1 to target, we look at each number x in our array. If x <= i, then we can use x as the last number in our combination. The number of ways to do this equals the number of ways to make i - x, which we've already calculated.

By building up from smaller sums to larger ones, we avoid recalculating the same subproblems repeatedly, making our solution efficient.

Solution Approach

We implement the solution using dynamic programming with a bottom-up approach. Here's how the algorithm works:

Step 1: Initialize the DP array

  • Create an array f of size target + 1 where f[i] represents the number of combinations that sum to i
  • Set f[0] = 1 (one way to make sum 0: choose nothing)
  • Set all other values f[1] to f[target] as 0

Step 2: Fill the DP array

  • For each sum i from 1 to target:
    • For each number x in nums:
      • If i >= x (we can use x in our combination):
        • Add f[i - x] to f[i]
        • This means: all ways to make i - x can be extended by adding x to make i

Step 3: Return the result

  • Return f[target], which contains the total number of combinations that sum to target

Example walkthrough: Let's say nums = [1, 2, 3] and target = 4:

  • Initially: f = [1, 0, 0, 0, 0]
  • For i = 1: Check each number in nums
    • x = 1: f[1] += f[0] = 1
    • Result: f = [1, 1, 0, 0, 0]
  • For i = 2:
    • x = 1: f[2] += f[1] = 1
    • x = 2: f[2] += f[0] = 1
    • Result: f = [1, 1, 2, 0, 0]
  • For i = 3:
    • x = 1: f[3] += f[2] = 2
    • x = 2: f[3] += f[1] = 1
    • x = 3: f[3] += f[0] = 1
    • Result: f = [1, 1, 2, 4, 0]
  • For i = 4:
    • x = 1: f[4] += f[3] = 4
    • x = 2: f[4] += f[2] = 2
    • x = 3: f[4] += f[1] = 1
    • Result: f = [1, 1, 2, 4, 7]

The time complexity is O(target × n) where n is the length of nums, and the space complexity is O(target) for the DP array.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with nums = [1, 2] and target = 3 to illustrate the solution approach.

Initial Setup:

  • Create DP array: f = [0, 0, 0, 0] (indices 0 to 3)
  • Set f[0] = 1 (one way to make 0)
  • Result: f = [1, 0, 0, 0]

Building up each sum:

Sum = 1:

  • Check x = 1: Since 1 >= 1, we can use it
    • f[1] += f[1-1] = f[0] = 1
  • Check x = 2: Since 1 < 2, we can't use it
  • Result: f = [1, 1, 0, 0]
  • This represents: {1}

Sum = 2:

  • Check x = 1: Since 2 >= 1, we can use it
    • f[2] += f[2-1] = f[1] = 1
  • Check x = 2: Since 2 >= 2, we can use it
    • f[2] += f[2-2] = f[0] = 1
  • Result: f = [1, 1, 2, 0]
  • This represents: {1+1} and {2}

Sum = 3:

  • Check x = 1: Since 3 >= 1, we can use it
    • f[3] += f[3-1] = f[2] = 2
    • This adds combinations: {1+1+1} and {2+1}
  • Check x = 2: Since 3 >= 2, we can use it
    • f[3] += f[3-2] = f[1] = 1
    • This adds combination: {1+2}
  • Result: f = [1, 1, 2, 3]

Final Answer: f[3] = 3

The three combinations are:

  1. [1, 1, 1] - using 1 three times
  2. [1, 2] - using 1 then 2
  3. [2, 1] - using 2 then 1

Notice how the DP approach naturally accounts for different orderings. When we compute f[3], we consider adding 1 to all ways of making 2 (which includes both {1+1} and {2}), and separately consider adding 2 to all ways of making 1 (which is just {1}). This gives us all possible ordered combinations.

Solution Implementation

1class Solution:
2    def combinationSum4(self, nums: List[int], target: int) -> int:
3        # Initialize dp array where dp[i] represents number of combinations that sum to i
4        # dp[0] = 1 as there's one way to make 0 (empty combination)
5        dp = [1] + [0] * target
6      
7        # Build up the dp array for each value from 1 to target
8        for current_sum in range(1, target + 1):
9            # Try using each number in nums as the last number in combination
10            for num in nums:
11                # Only consider this number if it doesn't exceed current_sum
12                if current_sum >= num:
13                    # Add the number of ways to make (current_sum - num)
14                    # This represents using 'num' as the last element
15                    dp[current_sum] += dp[current_sum - num]
16      
17        # Return the number of combinations that sum to target
18        return dp[target]
19
1class Solution {
2    public int combinationSum4(int[] nums, int target) {
3        // Dynamic programming array where dp[i] represents the number of combinations that sum to i
4        int[] dp = new int[target + 1];
5      
6        // Base case: there's one way to make sum 0 (using no numbers)
7        dp[0] = 1;
8      
9        // Build up the dp array for each possible sum from 1 to target
10        for (int currentSum = 1; currentSum <= target; currentSum++) {
11            // For each number in the input array
12            for (int num : nums) {
13                // If the current number can be used to form currentSum
14                if (currentSum >= num) {
15                    // Add the number of ways to form (currentSum - num) to current position
16                    // This represents using 'num' as the last element in the combination
17                    dp[currentSum] += dp[currentSum - num];
18                }
19            }
20        }
21      
22        // Return the number of combinations that sum to target
23        return dp[target];
24    }
25}
26
1class Solution {
2public:
3    int combinationSum4(vector<int>& nums, int target) {
4        // dp[i] represents the number of combinations that sum up to i
5        vector<int> dp(target + 1, 0);
6      
7        // Base case: there's one way to make sum 0 (choose nothing)
8        dp[0] = 1;
9      
10        // Build up the dp table for each possible sum from 1 to target
11        for (int currentSum = 1; currentSum <= target; ++currentSum) {
12            // Try each number in the array as a potential element in the combination
13            for (int num : nums) {
14                // Check if we can use this number (currentSum must be >= num)
15                // Also check for integer overflow before adding
16                if (currentSum >= num && dp[currentSum - num] < INT_MAX - dp[currentSum]) {
17                    // Add the number of ways to form (currentSum - num) to current sum
18                    dp[currentSum] += dp[currentSum - num];
19                }
20            }
21        }
22      
23        // Return the number of combinations that sum to target
24        return dp[target];
25    }
26};
27
1/**
2 * Finds the number of possible combinations that add up to target.
3 * Numbers can be used multiple times and different sequences are counted as different combinations.
4 * 
5 * @param nums - Array of distinct positive integers to use in combinations
6 * @param target - The target sum to achieve
7 * @returns The number of possible combinations that sum to target
8 */
9function combinationSum4(nums: number[], target: number): number {
10    // Dynamic programming array where dp[i] represents the number of combinations that sum to i
11    const dp: number[] = Array(target + 1).fill(0);
12  
13    // Base case: there's one way to make sum 0 (use no numbers)
14    dp[0] = 1;
15  
16    // Build up the dp array for each sum from 1 to target
17    for (let currentSum = 1; currentSum <= target; currentSum++) {
18        // Try using each number in nums as the last number in the combination
19        for (const num of nums) {
20            // Only consider this number if it doesn't exceed the current sum
21            if (currentSum >= num) {
22                // Add the number of ways to make (currentSum - num) to our total
23                // This represents adding 'num' to all combinations that sum to (currentSum - num)
24                dp[currentSum] += dp[currentSum - num];
25            }
26        }
27    }
28  
29    // Return the number of combinations that sum to target
30    return dp[target];
31}
32

Time and Space Complexity

Time Complexity: O(n × target)

The code uses two nested loops:

  • The outer loop iterates from 1 to target, which runs target times
  • The inner loop iterates through all elements in nums, which runs n times (where n is the length of the array nums)
  • For each combination of i and x, the operation f[i] += f[i - x] takes O(1) time

Therefore, the total time complexity is O(target × n) or O(n × target).

Space Complexity: O(target)

The code creates a single array f of size target + 1 to store the dynamic programming states. This is the only additional space used that scales with the input size. Therefore, the space complexity is O(target).

Common Pitfalls

1. Confusing with Coin Change Problem (Combinations without Order)

Pitfall: A common mistake is treating this as a standard coin change problem where order doesn't matter. Developers might iterate through nums first (outer loop) and then through the target values (inner loop), which would count [1, 2] and [2, 1] as the same combination.

Incorrect approach:

# This counts combinations where order doesn't matter
for num in nums:
    for i in range(num, target + 1):
        dp[i] += dp[i - num]

Solution: Keep the current implementation where we iterate through target values first (outer loop) and nums second (inner loop). This ensures different orderings are counted as distinct combinations.

2. Integer Overflow in Other Languages

Pitfall: While Python handles large integers automatically, in languages like Java or C++, the intermediate results might overflow even though the final answer fits in 32-bit integer.

Solution: Use appropriate data types (like long in Java) during computation and cast to int only when returning the final result:

// Java example
long[] dp = new long[target + 1];
// ... computation ...
return (int) dp[target];

3. Not Handling Edge Cases

Pitfall: Forgetting to handle edge cases like:

  • Empty nums array
  • target = 0
  • Negative numbers in nums (which could cause infinite loops)

Solution: Add validation at the beginning:

def combinationSum4(self, nums: List[int], target: int) -> int:
    if not nums or target < 0:
        return 0
    if target == 0:
        return 1
  
    # Filter out numbers greater than target for optimization
    nums = [num for num in nums if num <= target]
    if not nums:
        return 0
  
    dp = [1] + [0] * target
    # ... rest of the code

4. Memory Optimization Missed Opportunity

Pitfall: Not recognizing when the target is very large but nums contains only small values, leading to unnecessary memory usage.

Solution: Consider using memoization with recursion for sparse cases:

def combinationSum4(self, nums: List[int], target: int) -> int:
    @lru_cache(None)
    def dfs(remaining):
        if remaining == 0:
            return 1
        if remaining < 0:
            return 0
      
        count = 0
        for num in nums:
            count += dfs(remaining - num)
        return count
  
    return dfs(target)

5. Inefficient Iteration

Pitfall: Not optimizing the inner loop by sorting nums and breaking early when a number exceeds the current sum.

Solution: Sort the array and use early termination:

nums.sort()  # Sort once at the beginning

for current_sum in range(1, target + 1):
    for num in nums:
        if num > current_sum:
            break  # No need to check larger numbers
        dp[current_sum] += dp[current_sum - num]
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which type of traversal does breadth first search do?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More