#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
*/
关于记录子问题的解从而避免重复计算,还可看动态规划的另一个简单例子: