算法 动态规划 【初识DP之硬币问题】

41 篇文章 1 订阅

一、动态规划题目类型

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;
}

 测试:

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值