动态规划系列之「leetcode322.零钱兑换」

322. 零钱兑换

给定不同面额的硬币coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

零钱兑换问题符合最优子结构,比如你想求 amount = 11 时的最少硬币数(原问题),如果你知道凑出 amount = 10 的最少硬币数(子问题),你只需要把子问题的答案加一(再选一枚面值为 1 的硬币)就是原问题的答案。因为硬币的数量是没有限制的,所以子问题之间没有相互制,是互相独立的。

如何列出正确的状态转移方程

首先要明确我们的目的:用最少的硬币组成目标金额

  1. 确定 base case

    这个很简单,显然目标金额 amount 为 0 时算法返回 0,因为不需要任何硬币就已经凑出目标金额了。

  2. 确定「状态」,也就是原问题和子问题中会变化的变量

    由于硬币数量无限,硬币的面额也是题目给定的,只有目标金额会不断地向 base case 靠近,所以唯一的「状态」就是目标金额 amount

  3. 确定「选择」,也就是导致「状态」产生变化的行为

    目标金额为什么变化呢,因为你在选择硬币,你每选择一枚硬币,就相当于减少了目标金额。所以说所有硬币的面值,就是你的「选择」。

  4. 明确 dp 函数/数组的定义

    这里直接一步到位,采用自底向上的做法,数组dp[amount]代表当目标金额为amount时需要的最少硬币数(即最终的解)。

    状态转移方程为:

    d p ( n ) = { 0 , n = 0 − 1 , n < 0 m i n { d p ( n − c o i n ) + 1 ∣ c o i n ∈ c o i n s } , n > 0 dp(n)= \begin{cases}0, \quad n = 0 \\-1, \quad n<0 \\min\{ dp(n-coin) + 1 \quad | \quad coin \in coins \},\quad n>0 \end{cases} dp(n)=0,n=01,n<0min{ dp(ncoin)+1coincoins},n>0

dp(n) 的定义:输入一个目标金额 n,返回凑出目标金额 n 的最少硬币数量。

PS:为啥 dp 数组初始化为 amount + 1 呢,因为凑成 amount 金额的硬币数最多只可能等于 amount(全用 1 元面值的硬币),所以初始化为 amount + 1 就相当于初始化为正无穷,便于后续取最小值。

import java.util.*;

class Solution {
    
    
    public int coinChange(int[] coins, int amount) {
    
    
        int[] dp = new int[amount+1];
        Arrays.fill(dp, amount+1);
        dp[0] = 0;
        for(int i = 0; i <= amount; i++){
    
    
            for(int coin:coins){
    
    
                if(i-coin >= 0){
    
    
                    dp[i] = Math.min(dp[i-coin]+1, dp[i]);
                }
            }
        }
        return (dp[amount] == amount+1) ? -1 : dp[amount];
    }
}

时间复杂度:O(kN)k为硬币面值的种类数

空间复杂度:O(N)

猜你喜欢

转载自blog.csdn.net/weixin_44471490/article/details/109014424
今日推荐