Taro Logo

Coin Change

Medium
Google logo
Google
1 view
Topics:
Dynamic Programming

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money. Your task is to write a function that returns the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. You may assume that you have an infinite number of each kind of coin.

Example 1:

Input: coins = [1, 2, 5], amount = 11
Output: 3
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3
Output: -1

Example 3:

Input: coins = [1], amount = 0
Output: 0

Clarifications:

  1. What should the function return if the coins array is empty?
  2. Are the coin values always positive integers?
  3. Is the amount always a non-negative integer?
  4. What is the maximum possible value for the amount?
  5. Can I assume that the coin values are sorted? (If not, does my algorithm need to sort them?)

Write a function to solve the coin change problem efficiently, considering potential edge cases and optimizing for time and space complexity. Explain the time and space complexity of your solution.

Solution


Coin Change Problem

Problem Description

You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money. The goal is to find the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. Assume that you have an infinite number of each kind of coin.

Naive Solution: Brute Force (Recursion)

A simple, but inefficient, approach is to try every possible combination of coins recursively. For each coin, we either include it or exclude it, and explore all resulting possibilities.

Code (Python):

def coin_change_recursive(coins, amount):
    if amount == 0:
        return 0
    if amount < 0:
        return -1

    min_coins = float('inf')
    for coin in coins:
        res = coin_change_recursive(coins, amount - coin)
        if res != -1:
            min_coins = min(min_coins, res + 1)

    return min_coins if min_coins != float('inf') else -1

Explanation:

  • Base cases: If the amount is 0, return 0 (no coins needed). If the amount is negative, return -1 (invalid).
  • Recursive step: For each coin, subtract it from the amount and recursively call the function. Keep track of the minimum number of coins needed.

Time Complexity: O(branches^depth), where branches is the number of coin denominations and depth is the amount. This leads to exponential time complexity. Specifically, O(n^amount), where n is the number of coins.

Space Complexity: O(amount) due to the call stack depth of the recursion.

Optimal Solution: Dynamic Programming

A much more efficient solution can be obtained using dynamic programming. We build a table dp where dp[i] stores the minimum number of coins needed to make up amount i. We iterate from 1 to amount, and for each amount i, we iterate through the coins. If a coin's value is less than or equal to i, we check if using that coin gives us a better result than the current minimum for dp[i].

Code (Python):

def coin_change_dp(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0

    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)

    return dp[amount] if dp[amount] != float('inf') else -1

Explanation:

  • Initialize dp array with infinity, except dp[0] = 0 (0 coins needed to make amount 0).
  • Iterate through each amount from 1 to amount.
  • For each amount i, iterate through the coins. If a coin's value is less than or equal to i, update dp[i] with the minimum of its current value and dp[i - coin] + 1.
  • Finally, return dp[amount] if it's not infinity, otherwise return -1.

Time Complexity: O(amount * n), where amount is the target amount and n is the number of coin denominations.

Space Complexity: O(amount), for the dp array.

Edge Cases

  1. Empty coins array: If the coins array is empty, and the amount is not zero, it's impossible to make the amount. Return -1.
  2. amount is 0: If the amount is 0, no coins are needed. Return 0.
  3. No combination of coins can make up the amount: In this case, the dynamic programming approach will result in dp[amount] remaining as infinity. Return -1.
  4. Large coin values: The algorithm should handle cases where coin values are large, up to the maximum integer value. The dynamic programming approach avoids integer overflow issues because it only adds 1 in each step.
  5. Duplicate coins: The problem statement specifies there are infinite copies of each coin, so having duplicate values in the coins array doesn't change the solution.