文章首發於 guanngxu 的個人部落格:動態規劃演算法優化例項——如何求解換錢的方法數
寫在前面
這是我的人生**面遇到的乙個面試題,是在去哪兒網二面遇到的,那時非常的緊張,還沒有複習,所以第一次面試理所應當的掛了。
文章對問題進行逐步的由簡到難進行優化,基本上是**,看懂**才能理解,也為類似問題提供了基本的解決思路。
題目描述:
讓你把一張整錢找零,即假設你擁有不同且不限量的小額錢幣,你需要統計共有多少種方法可以用手中的小額錢幣兌等額兌換一張大額錢幣。遞迴求解即:給定乙個元素為正數的集合(元素不重複)代表不同面值的錢幣,再給乙個整數,代表要找零的錢數,求共有多少種換錢方法?
現在有1、5、10元三種面值的紙幣,需要找零100元,那麼可以做如下分析:
用 0 張 5 元換,剩下的用 1、10 元換,最終方法數為 count0;
用 1 張 5 元換,剩下的用 1、10 元換,最終方法數為 count1;
......
用 100 張 5 元換,剩下的用 1、10 元換,最終方法數為 count100;
最終的換錢方法總數就為 count0 + count1 + ...... + count100。
根據上面的分析可以寫出下面的遞迴解決方案:
public
static
intcoin
(int money,
int target)
else
}// 用money[index, length-1]換錢,返回總的方法數
private
static
intslove
(int money,
int index,
int target)
else
}else
}return res;
}
優化遞迴
可以看到,上面的程式在執行時存在大量的重複過程,比如下面兩種情況,其後所求結果是一樣的。
兌換 100 元,已經使用了 0 張 1 元、1 張 2 元,剩下的用 5 元和 10 元兌換;
兌換 100 元,已經使用了 2 張 1 元、0 張 2 元,剩下的用 5 元和 10 元兌換;
可以發現,這兩種情況後面都是求解同一問題,重複的對同乙個問題求解,就造成了時間的浪費,因此我們可以考慮將已經計算過的結果存下來,避免重複的計算,所以有下面的優化方案。
public
static
intcoin
(int money,
int target)
else
}private
static
intslove
(int money,
int index,
int target,
int map[
])else
}else
else
}else}}
if(res ==0)
else
return res;
}
動態規劃
上面對遞迴方法的優化已經能看到動態規劃的影子了,這是乙個二維的動態規劃問題,我們定義dp[i][j]的含義為:使用money[0…i]的錢幣組成錢數j的方法數。所以可以得出以下面的動態規劃解法:
public
static
intcoin
(int money,
int target)
int dp[
]=newint
[money.length]
[target+1]
;// 第一列表示組成錢數為0的方法數,所以為1
for(
int i =
0; i < money.length; i++
)// 第一行表示只使用money[0]一種錢幣兌換錢數為i的方法數
// 所以是money[0]的倍數的位置為1,否則為0
for(
int i =
1; money[0]
* i <= target; i++
)for
(int i =
1; i < dp.length; i++)}
}return dp[money.length-1]
[target]
;}
繼續優化
可以發現上面的動態規劃解法有三層迴圈,因為是二維的動態規劃問題,前兩層沒辦法去掉,但是第三層依舊很耗時間,繼續優化可以得到下面的結果。
public
static
intcoin
(int money,
int target)
int dp[
]=newint
[money.length]
[target+1]
;for
(int i =
0; i < money.length; i++
)for
(int i =
1; money[0]
* i <= target; i++
)for
(int i =
1; i < money.length; i++)}
}return dp[money.length-1]
[target]
;}
空間壓縮
可以看到每次更新dp[i][j],dp[i][j]的值只與前一行和當前行前面的元素有關係,而我們只需要最後的乙個結果就行了,那麼前面存的元素實際上會造成空間的浪費,進一步可以在空間上進行優化。
我們只需要定義乙個一位陣列,然後對該陣列進行滾動更新就可以了,只要按照合適方向去更新陣列,同樣能達到上面的效果。
public
static
intcoin
(int money,
int target)
int dp=
newint
[target+1]
;// 第一行,只用money[0]兌換錢
// 所以只能兌換為money[0]的倍數,將這些位置置為1
for(
int i =
0; money[0]
*i <= target; i++
)for
(int i =
1; i < money.length; i++)}
}return dp[target]
;}
至此,優化結束,這個問題個人認為很值得記錄下來,很多筆試、面試題都可以按這個模子進行套,對於只需要最優解的動態規劃問題也可以套用上面的空間壓縮思路。 一道面試題
一道面試題 射擊運動員10發打中90環有多少種可能,請編寫程式計算出來,並列印出結果,0環和10環均有效。打中90環就是沒打中10環,所以打中90環跟打中10環的可能性是一樣的。然後開始遞迴狂打槍,一到10就記錄 if params i 10 在迴圈的控制中已經排除了大於10的可能性 i 10 pa...
一道面試題
前些時候在找工作,就在準備結束此次找工作歷程的時候,去了一家公司面試,去了之後技術經理直接帶到一台電腦旁,給了一張紙條,上面是這樣的題目 用c或c 來實現 1 建立一棵樹,該樹的深度是隨機的,每個節點的位元組點數是隨機的。2 給每個節點分配一段隨機大小的記憶體空間,給每個節點賦乙個隨機數。3 遍歷這...
一道面試題
如果n為偶數,則將它除以2,如果n為奇數,則將它加1或者減1。問對於乙個給定的n,怎樣才能用最少的步驟將它變到1。例如 n 61 n 60 n 2 30 n 2 15 n 16 n 2 8 n 2 4 n 2 2 n 2 1 public class myclass public static vo...