Bird
Raised Fist0
Interview Prepdp-knapsackmediumAmazonGoogle

Number of Ways to Make Change

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Steps
setup

Initialize dp array

Create a dp array of size amount+1 (6) initialized to 0, then set dp[0] = 1 because there is exactly one way to make amount 0 (using no coins).

💡 Setting dp[0] = 1 is the base case; it anchors the solution by defining that zero amount can be made in one way.
Line:dp = [0] * (amount + 1) dp[0] = 1
💡 The base case dp[0] = 1 allows subsequent computations to build on a valid starting point.
📊
Number of Ways to Make Change - Watch the Algorithm Execute, Step by Step
Watching the dp array update step-by-step reveals how each coin contributes to the total ways, making the dynamic programming approach intuitive.
Step 1/15
·Active fillAnswer cell
Initializing dp array
i\w012345
i=0100000
dp[0] = 1 (base case)
Item 0 - wt:1 val:0
i\w012345
i=0100000
dp array before coin=1 updates
Item 0 - wt:1 val:0
i\w012345
i=0110000
dp[1] updated
Item 0 - wt:1 val:0
i\w012345
i=0111000
dp[2] updated
Item 0 - wt:1 val:0
i\w012345
i=0111100
dp[3] updated
Item 0 - wt:1 val:0
i\w012345
i=0111110
dp[4] updated
Item 0 - wt:1 val:0
i\w012345
i=0111111
dp[5] updated
Item 1 - wt:2 val:0
i\w012345
i=0111111
dp array before coin=2 updates
Item 1 - wt:2 val:0
i\w012345
i=0112111
dp[2] updated
Item 1 - wt:2 val:0
i\w012345
i=0112211
dp[3] updated
Item 1 - wt:2 val:0
i\w012345
i=0112231
dp[4] updated
Item 1 - wt:2 val:0
i\w012345
i=0112233
dp[5] updated
Item 2 - wt:5 val:0
i\w012345
i=0112233
dp array before coin=5 updates
Item 2 - wt:5 val:0
i\w012345
i=0112234
dp[5] final answer
Item 2 - wt:5 val:0
i\w012345
i=0112234
Answer = 4

Key Takeaways

The dp array accumulates the number of ways to make each amount incrementally as coins are processed.

This insight is hard to see from code alone because the dp array updates are subtle and depend on previous states.

Processing coins in order and updating dp from coin value to amount ensures no double counting of combinations.

Visualizing the order of updates clarifies why the algorithm counts combinations correctly without permutations.

The base case dp[0] = 1 is crucial as it seeds the dp array and allows all subsequent computations to build on a valid starting point.

This foundational step is often overlooked but is essential for the correctness of the dynamic programming solution.

Practice

(1/5)
1. You need to find the minimum number of coins required to make up a given amount from an unlimited supply of given coin denominations. Which algorithmic approach guarantees an optimal solution for this problem?
easy
A. Greedy algorithm that picks the largest coin first until the amount is reached
B. Dynamic programming using a 1D array to store minimum coins needed for all amounts up to the target
C. Pure brute force recursion trying all combinations without memoization
D. Sorting coins and using binary search to find the best coin for each sub-amount

Solution

  1. Step 1: Understand problem constraints

    The problem requires minimum coins for any amount with unlimited coin usage, which fits unbounded knapsack pattern.
  2. Step 2: Identify algorithm that guarantees optimality

    Greedy fails for some coin sets; brute force is correct but inefficient; DP with 1D array efficiently computes minimum coins for all sub-amounts ensuring optimality.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    DP solves overlapping subproblems optimally [OK]
Hint: Unbounded knapsack DP ensures optimal minimum coins [OK]
Common Mistakes:
  • Assuming greedy always works
  • Ignoring memoization for efficiency
2. You are given a list of jobs where each job has a start time, an end time, and a profit. You want to schedule jobs to maximize total profit such that no two jobs overlap. Which algorithmic approach guarantees an optimal solution for this problem?
easy
A. Dynamic programming with jobs sorted by end time and binary search to find compatible jobs
B. Pure brute force recursion checking all subsets of jobs
C. Greedy algorithm that always picks the job with the earliest end time
D. Dynamic programming based on sorting jobs by start time and using prefix sums

Solution

  1. Step 1: Understand job scheduling constraints

    The problem requires selecting non-overlapping jobs to maximize profit, which is a classic weighted interval scheduling problem.
  2. Step 2: Identify the optimal approach

    Sorting jobs by end time allows binary searching for the last compatible job, enabling a DP solution that builds optimal profit incrementally.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Greedy fails on overlapping jobs with higher profit; DP with binary search handles all cases optimally [OK]
Hint: Sort by end time and use DP with binary search [OK]
Common Mistakes:
  • Assuming greedy by earliest end time always works
  • Sorting by start time only
  • Trying brute force without pruning
3. You are given an array of integers and a target integer. You want to assign either a plus or minus sign to each integer such that the resulting sum equals the target. Which algorithmic approach guarantees finding the total number of such assignments efficiently?
easy
A. Sorting the array and using two pointers to find pairs that sum to the target
B. Greedy algorithm that picks signs based on local sum minimization
C. Dynamic programming using a 0/1 knapsack-like approach with state representing achievable sums
D. Divide and conquer by splitting the array and combining results without memoization

Solution

  1. Step 1: Understand problem constraints

    The problem requires counting all sign assignments to reach a target sum, which involves exploring exponential combinations.
  2. Step 2: Identify suitable algorithmic pattern

    Greedy and two-pointer approaches fail because they do not consider all combinations. Divide and conquer without memoization is exponential. DP with states representing achievable sums efficiently counts all valid assignments.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    DP handles overlapping subproblems and sums efficiently [OK]
Hint: Counting sign assignments requires DP over sums [OK]
Common Mistakes:
  • Thinking greedy or sorting solves counting sign assignments
4. What is the time complexity of the optimal bottom-up dynamic programming solution for the Coin Change II problem with n coins and target amount amount?
medium
A. O(amount^2)
B. O(n + amount)
C. O(2^amount)
D. O(n * amount)

Solution

  1. Step 1: Identify loops in code

    Outer loop iterates over n coins, inner loop iterates over amount from coin to amount.
  2. Step 2: Calculate complexity

    Each dp[w] update is O(1), total updates are roughly n * amount, so time complexity is O(n * amount).
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Nested loops over coins and amounts confirm O(n * amount) [OK]
Hint: Count nested loops and their ranges [OK]
Common Mistakes:
  • Confusing amount with n
  • Forgetting recursion stack space
  • Assuming quadratic in amount
5. The following code attempts to solve the Partition to K Equal Sum Subsets problem using DP with bitmask tabulation. Identify the line containing the subtle bug that can cause incorrect results or infinite loops.
def canPartitionKSubsets(nums, k):
    total = sum(nums)
    if total % k != 0:
        return False
    target = total // k
    n = len(nums)
    nums.sort()
    if nums[-1] > target:
        return False

    dp = [-1] * (1 << n)
    dp[0] = 0

    for mask in range(1 << n):
        if dp[mask] == -1:
            continue
        for i in range(n):
            if (mask & (1 << i)) == 0 and dp[mask] + nums[i] <= target:
                next_mask = mask | (1 << i)
                dp[next_mask] = (dp[mask] + nums[i]) % target

    return dp[(1 << n) - 1] == 0
medium
A. Line: dp = [-1] * (1 << n)
B. Line: if dp[next_mask] == -1: (missing in this code)
C. Line: nums.sort()
D. Line: dp[next_mask] = (dp[mask] + nums[i]) % target

Solution

  1. Step 1: Identify missing condition

    The code lacks a check if dp[next_mask] is already set, so it overwrites states, causing incorrect results.
  2. Step 2: Pinpoint the buggy line

    The line assigning dp[next_mask] unconditionally overwrites previous valid states, breaking memoization.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Adding "if dp[next_mask] == -1:" before assignment fixes bug [OK]
Hint: Always check if dp state is unset before assignment [OK]
Common Mistakes:
  • Overwriting dp states without checking leads to incorrect answers
  • Not pruning symmetric states increases runtime