剑指 Offer II 103. 最少的硬币数目(322. 零钱兑换)
题目:
思路:
【1】使用树的回溯方式:
【2】动态规划(本质上是将每个位置的钱数都展示出来,方便当金额加上去后可以查找到没加该硬币时候的数量)
代码展示:
动态规划:
//时间15 ms击败39.70% //内存40.7 MB击败90.50% //时间复杂度:O(Sn),其中 S 是金额,n 是面额数。 //一共需要计算 O(S) 个状态,S 为题目所给的总金额。 //对于每个状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn) 的时间复杂度。 //空间复杂度:O(S)。数组 dp 需要开长度为总金额 S 的空间。 class Solution { public int coinChange(int[] coins, int amount) { int[] dp = new int[amount + 1]; //然后填充到数组中,//由于当金额为0的时候是不需要硬币的故为0 for (int k = 1; k < dp.length; k++){ dp[k] = 77777; } //这里是遍历每个金额数的最小使用硬币数,如果金额是100000虽然1-100000都需要遍历 //但是在计算机中这样遍历的速度是很快的 for (int i = 1; i <= amount; i++) { for (int j = 0; j < coins.length; j++) { //这里判断是面值小于金额才会计算,防止溢出数组 if (coins[j] <= i) { dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); } } } return dp[amount] == 77777 ? -1 : dp[amount]; } } //这里如果是采用默认的new int 产生的0花费远比自主定义初始值的要慢 //时间10 ms击败99.16% //内存40.9 MB击败67.15% class Solution { public int coinChange(int[] coins, int amount) { int[] dp=new int[amount+1]; for(int k=0;k<dp.length;k++){ dp[k]=77777; } dp[0]=0; for(int i=0;i<coins.length;i++){ for(int j=coins[i];j<=amount;j++){ dp[j]=Math.min(dp[j-coins[i]]+1,dp[j]); } } return dp[amount]==77777?-1:dp[amount]; } }
使用树的回溯方式:
//时间38 ms击败10.80% //内存41 MB击败60.61% //时间复杂度:O(Sn),其中 S 是金额,n 是面额数。 //一共需要计算 S 个状态的答案,且每个状态 F(S) 由于上面的记忆化的措施只计算了一次,而计算一个状态的答案需要枚举 n 个面额值,所以一共需要 O(Sn) 的时间复杂度。 //空间复杂度:O(S),我们需要额外开一个长为 S 的数组来存储计算出来的答案 F(S) 。 class Solution { public int coinChange(int[] coins, int amount) { if (amount < 1) { return 0; } return coinChange(coins, amount, new int[amount]); } private int coinChange(int[] coins, int rem, int[] count) { if (rem < 0) { return -1; } if (rem == 0) { return 0; } if (count[rem - 1] != 0) { return count[rem - 1]; } int min = Integer.MAX_VALUE; for (int coin : coins) { int res = coinChange(coins, rem - coin, count); if (res >= 0 && res < min) { min = 1 + res; } } count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min; return count[rem - 1]; } }