Problem Walkthrough

Combination Sum IV LeetCode 377 — DP Guide

Discover why LeetCode 377 counts ordered permutations instead of unordered combinations — and how the loop order in your DP table determines whether you get Combination Sum IV or Coin Change II

8 min read|

Combination Sum IV

LeetCode 377

Problem Overview: Count Ordered Combinations

LeetCode 377 Combination Sum IV asks: given an array of distinct positive integers nums and a positive integer target, return the number of possible combinations that add up to target. The problem statement uses the word "combinations," but the twist is that different orderings count as different combinations. So for nums = [1, 2, 3] and target = 4, [1, 1, 2] and [1, 2, 1] and [2, 1, 1] are all counted separately — giving a total of 7.

This is fundamentally a permutation-counting problem despite its name. Each number in nums can be used an unlimited number of times (unbounded), and the order of elements matters. This puts it firmly in the family of unbounded knapsack DP problems, alongside Coin Change (minimize coins) and Coin Change II (count unordered combinations).

The constraints are: 1 ≤ nums.length ≤ 200, 1 ≤ nums[i] ≤ 1000, all elements of nums are unique, 1 ≤ target ≤ 1000. The answer is guaranteed to fit in a 32-bit integer. No negative numbers or zero appear in nums, which simplifies the recurrence significantly.

  • Input: distinct positive integer array nums and integer target
  • Output: number of ordered sequences that sum to target
  • Order matters — [1,2] and [2,1] are different sequences
  • Unlimited use of each number (unbounded)
  • Constraints: nums.length ≤ 200, target ≤ 1000

Combinations vs Permutations Trap: Why Order Matters Here

The most dangerous pitfall in LeetCode 377 is taking the problem name literally. "Combination Sum IV" sounds like it should count unordered combinations, the way Combination Sum I (LeetCode 39) and Combination Sum II (LeetCode 40) do. But the problem statement explicitly says different orderings count as different combinations — so it is really a permutation count, not a combination count.

This distinction has a direct algorithmic consequence. In Coin Change II (LeetCode 518), which counts genuine unordered combinations, you iterate over coins in the outer loop and targets in the inner loop. This ensures each coin ordering is only counted once. In Combination Sum IV, you flip the loops: target is the outer loop and nums is the inner loop. This single loop-order swap is what changes the count from combinations to permutations.

Understanding this trap saves you from implementing the wrong algorithm. If you solve it with Coin Change II loop order, you will get the number of unordered subsets — typically a smaller number. For nums = [1, 2, 3] and target = 4, Coin Change II gives 4 (unordered), while Combination Sum IV gives 7 (ordered). Both are DP on the same 1D array; only the loop order differs.

💡

Key Distinction

Coin Change II (unordered): outer loop = coins, inner loop = target. Combination Sum IV (ordered): outer loop = target, inner loop = nums. One loop-order swap separates a combination count from a permutation count.

Recursive Approach with Memoization

The top-down recursive solution defines a function count(remaining) that returns the number of ordered sequences summing to remaining. The base case is count(0) = 1: an empty sequence is the one valid way to reach a sum of zero. For any remaining > 0, try each num in nums where num ≤ remaining, and sum up count(remaining - num) over all valid nums. If remaining < 0 we return 0, but since all nums are positive this never happens.

Without memoization, the recursion explores an exponential number of paths. With memoization (a dictionary or array memo of size target + 1), each subproblem count(i) is computed once and cached. The total work is O(target × n) — target distinct subproblems, each requiring O(n) work to iterate over nums.

This memoized recursion is functionally equivalent to the bottom-up DP. Both compute the same dp[i] values. The top-down version is often easier to reason about initially because the base case (target = 0 returns 1) is explicit and the recurrence mirrors the problem definition directly. However, the bottom-up version avoids Python recursion depth limits and is typically preferred in interviews.

  1. 1Define count(remaining) with a memo dict initialized to {0: 1}
  2. 2Base case: if remaining == 0 return 1
  3. 3If remaining in memo: return memo[remaining]
  4. 4Loop each num in nums where num <= remaining
  5. 5Accumulate total += count(remaining - num)
  6. 6Store memo[remaining] = total and return total
  7. 7Call count(target) for the answer

Bottom-Up DP: Build the Permutation Count Table

The bottom-up DP uses a 1D array dp of size target + 1, initialized to all zeros, with dp[0] = 1. The interpretation is: dp[i] = the number of ordered sequences of nums elements that sum to exactly i. The base case dp[0] = 1 means there is exactly one way to form a sum of zero: the empty sequence.

The recurrence is: for each i from 1 to target, and for each num in nums where num ≤ i, add dp[i - num] to dp[i]. This says: to form an ordered sequence summing to i, take any valid num as the last element and prepend any sequence summing to i - num. Since we iterate over all nums at each target value i, we naturally count every ordering — the sequence ending in 1 then 2, the sequence ending in 2 then 1, etc.

After filling the array from dp[1] to dp[target], the answer is dp[target]. For nums = [1, 2, 3] and target = 4, the dp array builds up as: dp[1]=1, dp[2]=2, dp[3]=4, dp[4]=7. Each step uses previously computed values, making this an efficient one-pass bottom-up computation.

ℹ️

DP Table Walkthrough

For nums=[1,2,3], target=4: dp[0]=1, dp[1]=dp[0]=1, dp[2]=dp[1]+dp[0]=2, dp[3]=dp[2]+dp[1]+dp[0]=4, dp[4]=dp[3]+dp[2]+dp[1]=7. Final answer: dp[4]=7.

Why Loop Order Matters: The Key Insight

The loop order in the DP is the central insight of this problem family. When you iterate outer=target (i from 1 to target) and inner=nums (each num in nums), you allow every num to be placed at every position relative to all others. At each target sum i, you consider placing each num last — and the count of valid sequences ending in num is dp[i - num]. Summing this over all nums gives dp[i], counting every ordered arrangement.

When you swap to outer=nums and inner=target (as in Coin Change II), you process each coin denomination exhaustively before moving to the next. This means coin 1 is placed before coin 2 in the order of consideration, which prevents counting [2,1] separately from [1,2]. You get the number of multisets (unordered collections) that sum to target — exactly what Coin Change II asks for.

The same 1D dp array, the same recurrence dp[i] += dp[i - num], but a different loop order produces two fundamentally different answers. Outer=target gives permutations (order-sensitive count). Outer=nums gives combinations (order-insensitive count). This is one of the most elegant and memorable insights in DP problem families, and understanding it lets you solve both LeetCode 377 and LeetCode 518 from first principles without memorizing separate algorithms.

  1. 1Outer loop = target (i from 1 to target), inner loop = nums → permutation count (Combination Sum IV)
  2. 2Outer loop = nums, inner loop = target → combination count (Coin Change II)
  3. 3Both use: dp[i] += dp[i - num] (same recurrence)
  4. 4Both use: dp[0] = 1 (same base case)
  5. 5Only the nesting order of the two for-loops differs
  6. 6Remembering: "target outer = order matters" is the mnemonic

Code Walkthrough — Python and Java

Python implementation: initialize dp = [0] * (target + 1) with dp[0] = 1. Then use two nested loops: for i in range(1, target + 1): for num in nums: if i >= num: dp[i] += dp[i - num]. Return dp[target]. This is 5 lines of logic. Time complexity is O(target × len(nums)). Space complexity is O(target) for the 1D dp array.

Java implementation: int[] dp = new int[target + 1]; dp[0] = 1; for (int i = 1; i <= target; i++) { for (int num : nums) { if (i >= num) dp[i] += dp[i - num]; } } return dp[target]; The structure mirrors Python exactly. Both handle the edge case of num > i via the guard condition, ensuring no out-of-bounds access into the dp array.

Edge cases to watch: if target is 0, the answer is 1 (the empty sequence). If no num in nums is ≤ target, the answer is 0 (dp[target] stays 0). The problem guarantees all nums are positive, so num = 0 is never in the input — no infinite loops. The answer fits in a 32-bit integer per constraints, so no overflow handling is needed in most languages.

⚠️

Overflow Risk

For follow-up questions with negative numbers in nums (LeetCode 377 follow-up), dp values can grow unboundedly and cause infinite loops if not bounded. Always check constraints: if negative numbers are possible, you need cycle detection or capping. For the standard problem (all nums positive), there is no overflow risk within the given constraints.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now