#include <iostream>
using namespace std;
/**
題目描述:如果我們有面值爲1元、3元和5元的硬幣若干枚,最少要用多少枚硬幣湊夠n元?
*/
/**
解題分析:
(1)要算n元要多少個湊完[f(n)],那我分類嘛:
1.首先拿一個1元的硬幣,所以這個分類所需最少硬幣數爲f(n-1) + 1
2.首先拿一個3元的硬幣,這個分類所需最少硬幣數爲f(n-3) + 1
3.首先拿一個5元的硬幣,這個分類所需最少硬幣數爲f(n-5) + 1
對着三種分類分別求其湊的硬幣數,然後取最小值就是最終結果:
result = min{ f(n-1)+1, f(n-3)+1, f(n-5)+1 };
(2)然後就很容易了,就是遞歸,只要設置好遞歸結束的邊界條件即可
*/
int min(int a, int b, int c)
{
int min = a;
if(b < min)
min = b;
if(c < min)
min = c;
return min;
}
//普通遞歸法/分治法
int recursion(int n)
{
if(n == 5 || n == 3 || n == 1)
return 1;
if(n == 4 || n == 2)
return 2;
if(n <= 0)
return 0;
//最優子結構
return min(recursion(n - 1) + 1,
recursion(n - 3) + 1,
recursion(n - 5) + 1);
}
/**
事實上在《算法導論》一書中強調動態規劃算法核心點在於兩個:
(1)最優子結構
(2)重疊子問題
上面只是滿足了最優子結構,但是重疊的子問題重複計算了;
把這部分效率也進行優化才能算是真正的動態規劃算法,
否則只是普通遞歸/分治法。
*/
//避免重複計算子問題->將子問題的解保存下來
int F[20]={0};
int Dynamic_Programming(int n)
{
if(n == 5 || n == 3 || n == 1)
return 1;
if(n == 4 || n == 2)
return 2;
if(n <= 0)
return 0;
if(F[n] != 0)//表明已經計算過該子問題
return F[n];
//記錄子問題的解,然後才return
F[n] = min(Dynamic_Programming(n - 1) + 1,
Dynamic_Programming(n - 3) + 1,
Dynamic_Programming(n - 5) + 1);
return F[n];
}
int main()
{
int n;
while(cin>>n)
cout<<"When n = "<<n<<" ,result is: "<<Dynamic_Programming(n)<<endl;
return 0;
}
/**
示例輸入輸出:
-1
When n = -1 ,result is: 0
0
When n = 0 ,result is: 0
1
When n = 1 ,result is: 1
2
When n = 2 ,result is: 2
3
When n = 3 ,result is: 1
4
When n = 4 ,result is: 2
5
When n = 5 ,result is: 1
6
When n = 6 ,result is: 2
7
When n = 7 ,result is: 3
8
When n = 8 ,result is: 2
9
When n = 9 ,result is: 3
10
When n = 10 ,result is: 2
11
When n = 11 ,result is: 3
*/
關於記錄子問題的解從而避免重複計算,還可看動態規劃的另一個簡單例子: