top down LTE

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        def dp(n):
            if n == 0:
                return 0
            if n < 0 :
                return -1
            res = float('inf')
            for coin in coins:
                if dp(n-coin) < 0 :
                    continue 
                res = min(res,dp(n-coin)+1)
            return res if res != float('inf') else -1
        return dp(amount)

Top down solution

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # time O(NM), N = amount M = len(coins) ; space O(S)
        # step1: create a memo 
        memo = {}
        def dp(n):
            # step 2: check if n in memo
            if n in memo:
                return memo[n]
            # step 0 : base case1 -- if n == 0 
            if n == 0:
                return 0
            #step 0.5 : base case2 -- if n < 0
            if n < 0 :
                return -1
            # step3: initialize current decision var
            res = float('inf')
            # step4: solve subproblem-- loop through choices 
            for coin in coins:
                sub = dp(n-coin) # cache dp(n-coin) to sub can optimize runtime 
                # step4.1: early determination n-coin < 0 ==> return -1
                if sub == -1 :
                    continue
                # step4.2 update curren decision:  compare change or no change 
                res = min(res,sub+1)
                
            #step5 update final changes 
            memo[n] = res if res != float('inf') else -1
            
            return memo[n]
        
        return dp(amount)

Bottom up solution

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # time O(kN) space O(N), N = amount, k = len(coins)
        # space O(N)
        # dp[i]: each ammount need mininum changes 
        # why initialize value is amount + 1 ?  because at most can all be changed with $1 coin. 
        # step1: initialize dp max val 
        dp = [amount + 1]  * (amount + 1)
        # step2: base case 
        dp[0] = 0
        # step3: loop dp 
        for i in range(len(dp)):
            #step3.2  iterate choices
            for coin in coins:
                #step3.3 early termination
                if i-coin < 0:
                    continue
                # step3.4 find current minimun coin change best choice
                dp[i] = min(dp[i-coin] +1,dp[i]) 
        # step4 check if dp[amount] changed, return valid dp[amount]
        return dp[amount] if dp[amount] != (amount + 1) else -1

################# non shorten space version