[LeetCode解題] -- 零錢兌換
動態規劃是 求解最優化問題的一種常用策略
零錢兌換 322. 零錢兌換
[ 題目描述 ]
給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函式來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。
你可以認為每種硬幣的數量是無限的。
示例 1:
輸入:coins = [1, 2, 5], amount = 11
輸出:3
解釋:11 = 5 + 5 + 1
示例 2:
輸入:coins = [2], amount = 3
輸出:-1
示例 3:
輸入:coins = [1], amount = 0
輸出:0
示例 4:
輸入:coins = [1], amount = 1
輸出:1
示例 5:
輸入:coins = [1], amount = 2
輸出:2
一、貪心求解
貪心只看眼前最優,但不一定得到全域性最優解。如:41 = 25 10 5 1 最優解 20 20 1
如果沒有如下限制
1. 如果面值從1開始,此時一定有解(最壞情況 零錢面值都是1)
則此時如下條件一定可以不滿足: 如果沒有任何一種硬幣組合能組成總金額,返回 -1。( 即如果amount數額湊不齊,返回 -1 )
2. coins 陣列逆序排序
[ 解題思路 ]
如下解題思路為解決 陣列逆序排序問題,可忽略
陣列逆序排序,需要 int 是包裝型別 Integer
1. Collections.reverseOrder()
// 可以用此方法進行逆序排序,但是int陣列必須是Integer包裝類 型別
Arrays.sort(coin, Collections.reverseOrder());
2. 傳入比較器
// 簡寫如下
Arrays.sort(coins, (Integer f1, Integer f2) -> f2 - f1); // 傳入比較器,逆序排序 {25, 5, 10, 1}
即
Arrays.sort(coins,(Integer f1,Integer f2)->{
return f1-f2;
});
當然可以先將其轉為包裝類
Integer[] coin = new Integer[coins.length];
for (int i = 0; i <coins.length ; i++) {
coin[i]=coins[i];
}
[ 程式碼實現 ]
static int coinChange1(int[] coins, int amount) {
int res = 0, i = 0; // res面值可用數量 i 第i個
while (i < coins.length) {
if (amount < coins[i]) { //逆序,先取出最大的face[0]=25 剩下的錢 < 當前值。面值不可用
i++; // 面值不可用數量
continue;
}
amount -= coins[i]; // 面值是可用的
res++;
}
return res;
}
[ 效能分析 ]
二、遞迴求解
如果沒有如下限制
1. 如果沒有任何一種硬幣組合能組成總金額,返回 -1。( 即如果amount數額湊不齊,返回 -1 )
[ 解題思路 ]
參考 斐波那契
// 此處類別 fib 1. fib有2個選項 零錢兌換有4個選項 2.都有重複計算--記憶化搜尋 優化
static int fib(int n){
if(n<=2)
return 1;
return fib(1)+fib(2);
}
[ 程式碼實現 ]
解法1 暴力遞迴
/**
* 暴力遞迴(自頂向下的呼叫,出現了重疊子問題)
*
* 貪心策略得到的並非最優解 只看眼前
*
* dp(20) 湊到20分需要的最少硬幣個數
*
* dp(41) 湊到41分需要的最少硬幣個數
*
* dp(n) 湊到n分需要的最少硬幣個數
*
* 第一次選擇了25分的硬幣,現在還差 n-25分
* dp[n-25]湊到n-25需要的最少硬幣個數
* 如果第一次選擇的了25分的硬幣,那麼 dp(n)=dp(n-25)+1 // +1,已經選擇了1枚硬幣,25分的硬幣
*/
static int coinChange2_(int n) {
if (n < 1) return Integer.MAX_VALUE;
if (n == 25 || n == 20 || n == 5 || n == 1) return 1;
int min1 = Math.min(coinChange2_(n - 25), coinChange2_(n - 20));
int min2 = Math.min(coinChange2_(n - 5), coinChange2_(n - 1));
return Math.min(min1, min2) + 1;
}
解法2 記憶化搜尋優化
/**
* 記憶化搜尋(自頂向下的呼叫)
* dp[1]=dp[5]=dp[20]=dp[25]=1 湊夠1、5、20、25分需要的最少硬幣個數都是 1 枚
*/
static int coinChange2(int n) {
if (n < 1) return -1;
int[] dp = new int[n + 1]; // 需要計算到湊過n分,下標要取到n,故長度為n+1
int[] faces = {1, 5, 20, 25};
for (int face : faces) { // dp[1]=dp[5]=dp[20]=dp[25]=1; 直接賦值有bug。如果n=20,dp[25]越界
if (n < face) break;
dp[face] = 1;
}
return coinChange2(n, dp);
}
static int coinChange2(int n, int[] dp) {
if (n < 1) return Integer.MAX_VALUE;
if (dp[n] == 0) { // 陣列預設是0 dp[n] == 0 表示沒有計算過
int min1 = Math.min(coinChange2(n - 25, dp), coinChange2(n - 20, dp));
int min2 = Math.min(coinChange2(n - 5, dp), coinChange2(n - 1, dp));
dp[n] = Math.min(min1, min2) + 1;
}
return dp[n]; // 已經算過,直接返回。記憶化搜尋
}
// 此處類別 fib 1. fib有2個選項 零錢兌換有4個選項 2.都有重複計算--記憶化搜尋 優化
static int fib(int n){
if(n<=2)
return 1;
return fib(1)+fib(2);
}
[ 效能分析 ]
三 動態規劃求解
[ 解題思路 ]
[ 程式碼實現 ]
解法1
如果沒有如下條件限制
1. 如果沒有任何一種硬幣組合能組成總金額,返回 -1。( 即如果amount數額湊不齊,返回 -1 )
/*
dp[n] 湊夠n分需要的最少硬幣個數
dp[i]=min{dp[i-1],dp[i-5],dp[i-20],dp[i-25]}+1
解讀:dp[i] 湊夠i分需要的最少硬幣個數 = 湊夠 {,,,}最小個數,再+1個硬幣
coins={5,20,25}
amount=5 dp=[0, 0, 0, 0, 0, 1]
amount=6 dp=[0, -2147483648, -2147483648, -2147483648, -2147483648, 1, -2147483647]
*/
static int coinChange3_1(int[] coins,int amount) {
if (amount < 1 || coins == null || coins.length == 0)
return -1;
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) {
int min = Integer.MAX_VALUE;
for (int coin : coins) {
if (i < coin)
continue;
min = Math.min(dp[i-coin],min);
}
dp[i]=min+1;
}
System.out.println(Arrays.toString(dp));
return dp[amount];
}
解法2
// dp[n] 湊夠n分需要的最少硬幣個數
/*
// 如果amount數額湊不齊。即:如果沒有任何一種硬幣組合能組成總金額,返回 -1。
coins={5,20,25}
amount=5 dp=[0, -1, -1, -1, -1, 1]
amount=6 dp=[0, -1, -1, -1, -1, 1, -1]
amount=20 dp=[0, -1, -1, -1, -1, 1, -1, -1, -1, -1, 2, -1, -1, -1, -1, 3, -1, -1, -1, -1, 1]
*/
static int coinChange3_2(int[] coins,int amount) {
if (amount < 1 || coins == null || coins.length == 0)
return -1;
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) {
int min = Integer.MAX_VALUE;
for (int coin : coins) {
if (i < coin) // 示例 i=1 faces={5,20,25}沒有1,此時dp[1]=-1 dp[1~4]=-1
continue;
int v = dp[i - coin];
// 如果面值不是從1開始,那麼湊夠dp[i]需要的最少硬幣個數v就是-1.即:如果{5,20,25},dp[1~4]=-1
// 如果湊夠dp[i]需要的最少硬幣個數v >=min ,那麼沒有必要更新min.即:如果{20,5,25},amount=20
// v=dp[20-20]=0 (湊夠0分需要0枚);或者那麼v=dp[20-15]=3 (湊夠15分需要3枚)
// 則 v=3 答案捨棄
if (v < 0 || v >= min) // v=-1 即沒法湊
continue;
min = v;
}
if (min == Integer.MAX_VALUE) { // 只要湊不齊,dp[i=-1]
dp[i] = -1;
} else {
dp[i] = min + 1;
}
}
System.out.println(Arrays.toString(dp));
return dp[amount];
}
3 完整程式碼
static int coinChange(int[] coins,int amount) {
if (amount < 1 || coins == null || coins.length == 0)
return -1;
int[] dp = new int[amount + 1];
for (int i = 1; i <= amount; i++) {
int min = Integer.MAX_VALUE;
for (int coin : coins) {
if (i < coin)
continue;
int v = dp[i - coin];
if (v < 0 || v >= min) //v < 0 即沒辦法湊;v >= min已經有更優解
continue;
min = v;
}
if (min == Integer.MAX_VALUE) { // 湊不齊,dp[i=-1]
dp[i] = -1;
} else {
dp[i] = min + 1;
}
}
System.out.println(Arrays.toString(dp));
return dp[amount];
}
[ 效能分析 ]
相關文章
- Leetcode 322 零錢兌換LeetCode
- leetcode322 零錢兌換LeetCode
- 【LeetCode】322. 零錢兌換LeetCode
- 每日一算--零錢兌換
- 力扣-322. 零錢兌換力扣
- 【程式碼隨想錄】零錢兌換
- 【LeetCode動態規劃#08】完全揹包問題實戰與分析(零錢兌換II)LeetCode動態規劃
- leedcode518:完全揹包,零錢兌換,python逐行註解Python
- 從【零錢兌換】問題看01揹包和完全揹包問題
- [Python手撕]零錢兌換(組合總數,需要去重)Python
- 程式碼隨想錄day 38 || 322 零錢兌換,279 完全平方數,139 單詞拆分
- 積分商品兌換
- 程式碼隨想錄演算法訓練營 | 322. 零錢兌換,279.完全平方數,139.單詞拆分演算法
- [leetcode 題解] 849LeetCode
- Leetcode 全套題解LeetCode
- LeetCode題解第122題LeetCode
- LeetCode每日一題: 移動零(No.283)LeetCode每日一題
- Leetcode 565 & 240 題解LeetCode
- LeetCode 解題彙總LeetCode
- 怎麼樣進行貨幣兌換?
- Leetcode題解1-50題LeetCode
- leetcode題解(陣列問題)LeetCode陣列
- Currency Assistant for Mac 貨幣兌換計算器Mac
- leetcode題解(查詢表問題)LeetCode
- 程式碼隨想錄演算法訓練營第四十四天 | 322.零錢兌換 279.完全平方數 139.單詞拆分演算法
- 【leetcode 399 周賽】【題解】LeetCode
- leetcode題解【持續更新】LeetCode
- LeetCode每日一題: 檸檬水找零(No.860)LeetCode每日一題
- LeetCode每日一題: 階乘後的零(No.172)LeetCode每日一題
- 用指令碼整理Leetcode題解指令碼LeetCode
- leetcode題解(動態規劃)LeetCode動態規劃
- 力扣 (LeetCode) - Database-刷題626--換座位力扣LeetCodeDatabase
- leetcode:面試題 01.08. 零矩陣(陣列,中等)LeetCode面試題矩陣陣列
- leetcode陣列練習題2:283. 移動零LeetCode陣列
- leetcode題解(0-1揹包問題)LeetCode
- LeetCode解題記錄(雙指標專題)LeetCode指標
- leetcode 解題 2.兩數相加-python3 題解LeetCodePython
- LeetCode:移動零(java)LeetCodeJava