一道题讲解动态规划的前世今生

题目:给一个数组Arr=[2,3,5,7,5],aim=10;得出数组中组成aim的元素个数最少是多少;
下面从三个方面依次解题,逐步从递归解法过渡到动态规划

1. 暴力递归

  • 思路
    设f(index,aim)表示从index开始的硬币中,组成aim的最小硬币数;那么我们的目标为f(0,10);
f(index,aim) = min(f(index+1,aim),1+f(index+1,aim-Arr[index]))

第一项表示不用第index个硬币;第二项表示使用index个硬币

/********************暴力递归****************/

//index表示从index个硬币开始选择
//rest表示还剩下多少钱数需要凑
//返回index开始以后的硬币还需要使用rest个
int process1(vector<int>& Arr, int index, int rest)
{
	if (rest < 0)
		return -1;
	if (rest == 0)
		return 0;
	//rest>0但是硬币没了
	if (index == Arr.size())
		return -1;
	//rest>0并且还有硬币
	int p1 = process1(Arr, index + 1, rest);
	int p2Next = process1(Arr, index + 1, rest - Arr[index]);
	if (p1 == -1 && p2Next == -1)
		return -1;
	else if (p1 == -1)
		return p2Next + 1;
	else if (p2Next == -1)
		return p1;
	return min(p1, 1 + p2Next);
}
//得到最小的硬币数
int get_mincoin1(vector<int>& Arr, int aim)
{
	return process1(Arr, 0, aim);
}

在这里插入图片描述

2. 记忆搜索解法

第一种解法,会出现重复计算的情况,如同f(3,5)会在两个分支上都计算一遍,这就造成了计算资源的浪费;所以记忆搜索解法相比第一种暴力递归解法添加存储机制,保存已经计算过的f(index,rest)的值;当再次遇到的时候,直接使用,不在重复计算

  • code
/*************记忆搜索*************/

int process2(vector<int>& Arr, int index, int rest,vector<vector<int>>& dp)
{
	if (rest < 0)
		return -1;
	if (dp[index][rest] != -2)
		return dp[index][rest];
	if (rest == 0)
		dp[index][rest] = 0;
	//rest>0
	else if (index == Arr.size())
		dp[index][rest] = -1;
	else
	{
		//rest>0 还有硬币
		int p1 = process2(Arr, index + 1, rest,dp);
		int p2Next = process2(Arr, index + 1, rest - Arr[index],dp);
		if (p1 == -1 && p2Next == -1)
			dp[index][rest] = -1;
		else if (p1 == -1)
			dp[index][rest] = p2Next + 1;
		else if (p2Next == -1)
			dp[index][rest] = p1;
		else
			dp[index][rest] = min(p1, 1 + p2Next);
	}
	return dp[index][rest];
}

int get_mincoin2(vector<int>& Arr, int aim)
{
	vector<vector<int>> dp(Arr.size() + 1, vector<int>(aim + 1, -2));
	return process2(Arr, 0, aim, dp);
}

3. 严格表结构

严格表结构就是大家说的动态规划,状态转移矩阵;在从记忆搜索解法往严格表结构转换的时候,需要有一下几个步骤
1) 找出可变参数个数和范围,用于定义dp数组;
2) 需要根据记忆搜索算法的if条件,确定dp数组中不需要计算的位置;
3) 寻找表中其他位置的位置依赖的规律

状态转移矩阵的大概形式(还需要根据细节划分)

dp(index,aim) = dp(f(index+1,aim),1+dp(index+1,aim-Arr[index]))
  • code
/*************严格表结构*************/
int get_mincoin3(vector<int>& Arr, int aim)
{
	vector<vector<int>> dp(Arr.size() + 1, vector<int>(aim + 1,0));
	for (int index = 0; index < Arr.size(); index++)
		dp[index][0] = 0;
	for (int rest = 1; rest <= aim; rest++)
		dp[Arr.size()][rest] = -1;

	for (int index = Arr.size() - 1; index >= 0; index--)
	{
		for (int rest = 1; rest <= aim; rest++)
		{
			


			int p1 = dp[index + 1][rest];
			int p2Next = -1;
			if(rest - Arr[index] >= 0)
				p2Next = dp[index + 1][rest - Arr[index]];

			if (p1 == -1 && p2Next == -1)
				dp[index][rest] = -1;
			else if (p1 == -1)
				dp[index][rest] = p2Next + 1;
			else if (p2Next == -1)
				dp[index][rest] = p1;
			else
				dp[index][rest] = min(p1, 1 + p2Next);
		}
	}
	return dp[0][aim];;
}

4. 总结

实际上,动态规划题目最终的状态转移矩阵的本质都是由第一解法暴力递归转化过来的;最重要的还是要确定递归的思路,而递归思路的确定的前提,就是找到变换的参数index和rest;所以最重要的就是找到这两个变化的参数和递归的思路,然后状态转移矩阵就可以迎刃而解;
动态规划中,重要的还有遍历的顺序和变换参数的范围

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星光技术人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值