【演算法】找零錢-動態規劃實現過程解析
本文章素材來自:https://www.nowcoder.com/study/vod/1/12/1
題目要求:
有陣列penny,penny中所有的值都為正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一個整數aim(小於等於1000)代表要找的錢數,求換錢有多少種方法。
給定陣列penny及它的大小(小於等於50),同時給定一個整數aim,請返回有多少種方法可以湊成aim。
假設測試資料如下:
int[] penny=new int[]{5, 15, 25, 1};
int aim=1000;
1、暴力求解法(利用遞迴的思想)
實現思想:根據上面的測試的資料,有如下想法
因此可以定義遞迴過程:
具體的演算法在這裡就不貼出來來,可以參考第2點的演算法實現。
2、記憶化搜尋法(在暴力求法的基礎上修改的)
public static int countWays(int[] penny, int aim) {
// write code here
if (penny == null || penny.length == 0 || aim < 0) return 0;
else {
HashMap<String, Integer> map = new HashMap<>();
return fun( map,penny, 0, aim);
}
}
public static int fun(HashMap<String,Integer> map, int[] arr, int index, int aim) {
if (index == arr.length) {
//如果aim剛好為0了,就表示找到了一個方案,否則penng陣列中的幣值用完了aim還有剩餘就表示方案行不通,即沒有對應的方案
return aim == 0 ? 1 : 0;
}
String key = index + "-" + aim;
if (map.containsKey(key)) return map.get(key);
int res = 0;
for (int i = 0; arr[index] * i <= aim; i++)
res += fun(map, arr, index + 1, aim - arr[index] * i);
map.put(key, res);
return res;
}
因為暴力求解時使用的遞迴,因此會存在重複的計算,因此在原基礎上使用一個資料結構將已經計算好的結果儲存起來,如果需要再次計算時就直接從裡面取出結果,這樣就可以避免遞迴的重複計算。
因此,在上述程式碼中,使用來一個HashMap
來儲存計算好的結果,其中Map的key是由index
和 aim
組成的,對應的value
即為已經計算過的結果。
3、由 記憶化搜尋引出的 動態規劃
O(n * aim^2)
public static int countWays(int[] penny, int aim) {
if (penny == null || penny.length == 0 || aim < 0) return 0;
int dp[][]=new int[penny.length][aim+1];
for (int i = 0; i < penny.length; i++) dp[i][0] = 1;
for (int i = 1; i <= aim; i++)
{
if (i % penny[0] == 0) dp[0][i] = 1;
else dp[0][i] = 0;
}
for (int i = 1; i < penny.length; i++)
{
for (int j = 1; j <= aim; j++)
{
int count = 0;
for (int k = 0; penny[i]*k <= j; k++)
count += dp[i-1][j-penny[i]*k];
dp[i][j] = count;
}
}
return dp[penny.length-1][aim];
}
由記憶搜尋法與其引出的動態規劃法的核心演算法的比較,發現並無太大的差異,而主要的區別就是如上圖所說的,動態規劃規定好了計算的順序(計算dp[i][j]
就要先計算出 dp[i-1][0 ~ j]
的結果,然後在列舉求和,而dp[i-1][0 ~ j]
每個元素的結果又需要由對應的上一排的列舉求和實現…),而記憶搜尋法本質還是遞迴,只不過優化了其過程,避免的重複的遞迴計算。
4、優化後的動態規劃
根據上圖的可以知道,需要累加的項只有dp[i-1][j-1*arr[i]]
以及其所在那一排的且位於它前面的某些項(這一部分就相當於dp[i][j-arr[i]
),以及dp[i-1][i]
這一項。因此可以優化原有動態規劃的實現,避免上一排列舉求和的操作。
注意:為什麼說只有部分項需要累加呢,這因為這些項之間都是依次相差 (arr[i]-1)個位置,而兩個項中間的元素的值,其實是為0。舉例說明,因為dp[i-1][j-1*arr[i]]
(表示使用1 張arr[i]貨幣),到 dp[i-1][j]
之間(表示完全不用使用arr[i]貨幣),其中包含了 j - arr[i]+1
、j - arr[i]+2
…、、j - arr[i]+(arr[i]-1)
,而這些是湊不齊1張arr[i]貨幣的,因此會有(零錢)剩餘,而剩餘就表示該方案行不通,即為0。
public static int countWays(int[] penny, int aim) {
if (penny == null || penny.length == 0 || aim < 0) return 0;
int dp[][]=new int[penny.length][aim+1];
for (int i = 0; i < penny.length; i++) dp[i][0] = 1;
for (int i = 1; i <= aim; i++)
{
if (i % penny[0] == 0) dp[0][i] = 1;
else dp[0][i] = 0;
}
for (int i = 1; i < penny.length; i++)
{
for (int j = 1; j <= aim; j++)
{
if (j < penny[i]) dp[i][j] = dp[i-1][j];
else dp[i][j] = dp[i-1][j] + dp[i][j-penny[i]];
}
}
return dp[penny.length-1][aim];
}
相關文章
- 動態規劃系列之九找零錢動態規劃
- 動態規劃5:找零錢問題動態規劃
- 演算法系列-動態規劃(3):找零錢、走方格問題演算法動態規劃
- 找零問題與動態規劃動態規劃
- 詳解動態規劃最少硬幣找零問題–JavaScript實現動態規劃JavaScript
- 詳解動態規劃最少硬幣找零問題--JavaScript實現動態規劃JavaScript
- 動態規劃演算法動態規劃演算法
- 演算法_動態規劃演算法動態規劃
- 演算法-動態規劃演算法動態規劃
- 演算法系列-動態規劃(1):初識動態規劃演算法動態規劃
- 動態規劃演算法原理與實踐動態規劃演算法
- Swift 演算法實戰之路:動態規劃Swift演算法動態規劃
- 前端演算法 - 動態規劃前端演算法動態規劃
- 動態規劃演算法(轉)動態規劃演算法
- 使用動態規劃完美解決硬幣找零問題(Python)動態規劃Python
- 演算法導論-動態規劃-裝配線排程演算法動態規劃
- 【每日演算法】動態規劃四演算法動態規劃
- 初級演算法-動態規劃演算法動態規劃
- Python演算法:動態規劃Python演算法動態規劃
- django 動態查詢實現過程Django
- 【動態規劃(一)】動態規劃基礎動態規劃
- 使用動態規劃 實現字元級Diff & Patch動態規劃字元
- 動態規劃實現O(n)爆破任意加密動態規劃加密
- 【演算法資料結構Java實現】Java實現動態規劃(揹包問題)演算法資料結構Java動態規劃
- 運籌優化(七)--動態規劃解析優化動態規劃
- 演算法(七):圖解動態規劃演算法圖解動態規劃
- 演算法之動態規劃總結演算法動態規劃
- Java 演算法-最大矩形(動態規劃)Java演算法動態規劃
- 演算法-動態規劃-完全揹包演算法動態規劃
- 動態規劃動態規劃
- 演算法---貪心演算法和動態規劃演算法動態規劃
- 演算法筆記之動態規劃(4)演算法筆記動態規劃
- 動態規劃演算法(DP)學習<1>動態規劃演算法
- 動態規劃之 KMP 演算法詳解動態規劃KMP演算法
- 矩陣連乘(動態規劃演算法)矩陣動態規劃演算法
- 【Python】(十二)從自動販賣機找零看Python中的動態規劃問題Python動態規劃
- PHP使用動態規劃實現最優紅包組合PHP動態規劃
- Python動態規劃實現虛擬機器部署Python動態規劃虛擬機