一、动态规划题目类型
1.计数问题
有多少种方式走到右下角
有多少种方法选出k个数,使和为sum
2.求最大值最小值
从左上角走到右下角路径的最大数字和
最长上升子序列长度
3.求存在性
取石子游戏,先手是否必胜
二、问题
题目:有三种硬币,面值2.5.7,买一本书需要27元,如何用最少的硬币整好付清。
很显然,这是一个求最大值最小值问题。
三、四步骤
1.确定状态
动态规划需要开辟一个数组,数组的每个元素f[i]或者f[i][j]代表什么,类似数学题中的未知数代表什么一样。
具体分为下面两个步骤:
1.研究最优策略的最后一步
2.化为子问题
2.写转移方程
根据子问题的问题定义直接可以得到。
取子问题和原问题中相同的部分即可。
3.初始条件和边界情况
初始条件,一般是f[0],f[1]这样的,自行判断
边界条件主要看数组是否越界。
4.计算顺序
利用之前的计算结果。
如我们要计算f[1],需要f[0]的结果,
那么计算顺序就是升序的。
四、硬币问题示例讲解
1.确定状态
1.1)研究最后一步
虽然不清楚最优策略是什么,
但是最后一步一定是:
最后一枚面值为K的硬币所用的1枚硬币数 + 前面27-k枚硬币所需要的最少硬币数。
K = 2 || 5 || 7。
1.2)化为子问题
原问题:如何用最少的硬币付清27元
子问题:如何用最少的硬币付清27-K元
得出状态:
如何用最少的硬币付清x元
也就是f[x] = 最少用多少枚硬币拼出x元。
2.写转移方程
3.确定初始条件和边界条件
3.1)初始条件
0枚硬币最少需要0枚硬币可以凑出。
也即f[0] = 0;
3.2)边界条件
首先,如果x-2,x-5,x-7的结果小于0了,
题意就成了,-1枚硬币最少需要多少枚硬币可凑出。
这显然不合理,而且数组无法开辟下标小于0的数。
所以我们需要在计算时进行一个判断,如果小于0,则不做处理。
其次,有拼不出的硬币,如3.
拼不出的硬币,我们就让它为正无穷。
那
f[3] = min{f[1] + 1,f[-2]+1,f[-4]+1};
这里的f[1]是有值的,为正无穷INT_MAX,
INT_MAX + 1会越界,
所以这里我们也需要判断。
所以思路清晰了:
我们可以先让每一个格子都为正无穷,f[0] = 0,
每一次都进行边界条件的判断,如果他的下标大于等于0,并且数组元素不会越界,
那么再对这个格子进行赋值,min的比较。
4.计算顺序
正序。
当我们计算到f[X]时,f[X-2],f[X-5],f[X-7]都已经得到结果了。
5.时间复杂度
每一次都尝试三种硬币:
如f[1]会尝试f[1] ,f[-2] ,f[-4].
而之前的值都被记录在了数组中,不必重复计算。
算法的时间复杂度是:27*3
凑N个硬币,一共M种硬币需要的步数:
N*M
五、代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <iostream>
using namespace std;
int minimum(int a, int b)
{
return a < b ? a : b;
}
int main()
{
//硬币种类数组
int coin_kind[] = { 2,5,7 };
int length = sizeof(coin_kind) / sizeof(coin_kind[0]);
//第一步:确定状态
//1.1 研究最后一步
//最后一步等于最后一枚硬币a的一个数量,加上前面27-a所需要的数量
//1.2 化为子问题
//f[x] = 最少用多少枚硬币可拼出x元
//数组大小为要凑的元数(因为需要从头遍历)+1(状态0)
const int number = 27;
int f[number + 1];
//第二步:写转移方程
//f[x] = min{f[x-coin_kind[0]] + 1 ,...,f[x-coin_kind[n-1]] + 1}
//第三步:初始条件与边界情况
//3.1 初始条件
f[0] = 0;
//3.2 边界情况
//3.2.1 数组下标必须大于等于零
//x-coin_kind[r] >= 0
//3.2.2 拼不出来的情况不算在考虑范围内
//f[x - coin_kind[r]] != INT_MAX
for (int i = 1; i <= number; i++)
{
f[i] = INT_MAX;
for (int j = 0; j < length; j++)
{
if (i - coin_kind[j] >= 0 && f[i - coin_kind[j]] != INT_MAX)
{
f[i] = minimum(f[i - coin_kind[j]] + 1, f[i]);
}
}
}
if (f[number] == INT_MAX)
{
f[number] = -1;
}
cout << "凑" << number << "个硬币最少需要" << f[number] << "枚硬币。"<<endl;
return 0;
}
测试: