完全背包问题Leecode322零钱兑换和Leecode518零钱兑换||

Leecode 322零钱兑换

题目大意
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。
题解思路
状态确定:dp[i]表示凑成面值i所需要的最小硬币数。从0开始遍历一直到amount,求出dp[amount]即为所求解。
状态方程:
dp[i]=min(dp[i-coins[j],dp[i]),i>=coins[i]
技巧:初始时给所有的dp[i]赋值为amount+1,最多硬币数是小于这个值的,如果遍历到amount,其值dp[amount]仍为amount+1,说明没有任何一种硬币组合数使其面值等于amount,则返回-1;在这个初始赋值为最大值时,所有
边界确定:dp[0]=0;
参考代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int coinChange(int* coins, int coinsSize, int amount){
int dp[amount+1];
for(int i=0;i<=amount;i++){
	dp[i]=amount+1;//便于后序检测是否有面值无法用该硬币组合数构成
}
dp[0]=0;
for(int i=1;i<=amount;i++){
	for(int j=0;j<coinsSize;j++){
		if(i>=coins[j])//如果当前待求面值大于或等于该硬币的值,即一定不能选该枚硬币,否则可选
			dp[i]=min(dp[i],dp[i-coins[j]]+1);
	}
}

if(dp[amount]>amount)
	return -1;
else return dp[amount];
}
int main(){
	int n;
	int coins[12],amount;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>coins[i];
	}
	cin>>amount;
	cout<<coinChange(coins,n,amount); 
	return 0;
} 

题目复盘
刚开始看到这道题目时就模仿着之前做的题目往二维套,写了下面这个思虑不周的错误代码

	
//	int dp[13][10001]={0};
//	for(int j=1;j<=amount;j++)
//		dp[0][j]=-1;
//	dp[1][1]=1;
//	for(int i=1;i<=coinsSize;i++){
//		for(int j=0;j<=amount;j++)
//		if(j<coins[i-1]&&dp[i-1][j]!=-1)
//			dp[i][j]=dp[i-1][j];
//		else if(dp[i-1][j]==-1)
//			dp[i][j]=-1;
//		else if(coins[i-1]<j)
//			dp[i][j]=min(dp[i-1][j],dp[i-1][j-coins[i-1]]+1);
//	}
//	return dp[coinsSize+1][amount];

该代码错误如下:
向之前那样一次遍历前i个硬币,这种默认硬币是只能使用一次,实际每个硬币可使用多次
之后看了题解用一位数组存储状态,因为初值赋的不合理,使后面思路凌乱,又写了以下错误代码:

	//一维动态规划
//	int dp[10000]={0};
//	//for(int i=0;i<coinsSize;i++) dp[coins[i]]=1;
//	for(int i=1;i<=amount;i++) 
//		{
//		 
//			int mi=10000;
//			for(int j=0;j<coinsSize;j++)
//            {
//            	if(i==coins[j]){
//            		dp[i]=1;
//            		break;
//				}     	  
//                if(i>coins[j])
//				 dp[i]=fmin(dp[i-coins[j]]+1,dp[i]);
                else if(i<coins[j])
                 dp[i]=fmin(dp[i],dp[i]);
//            }
//				
//            if(mi=-1)
//                dp[i]=-1;
//            else
//			dp[i]=mi+1;
//		}
//	return dp[amount];

希望下次做题时逻辑能更清晰一些,想的更明白一些,灵活运用动态规划这个思想!

Leecode 518零钱兑换II

题意

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。

解题思路

为了避免金币种类因顺序不同遍历导致的重复,此解题在外层先遍历金币的种类数,在内层遍历构成的金额数。这样构成每一金额的硬币都是按顺序排好的即可避免重复。用dp[i]存放使用前面几种类硬币构成的金额i的方案个数,在遍历到本层时再用dp[i]=dp[i]+dp[i-coins[j]]更新到达本层时构成金额i的种类数。
状态表示:dp[i]凑成金额i的组合数
状态转换: dp[i]=dp[i]+dp[i-coins[j]],i>=coins[j]
源码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int coinChange(int* coins, int coinsSize, int amount){
	//int dp[amount+1]={0};
	for(int i=0;i<=amount;i++)
		dp[i]=0; 
//	for(int i=0;i<=amount;i++)
//		dp[i]=amount+1; //给dp[i] 赋初值为一个不可能达到的值,如果最后真的等于这个值说明dp[i]不可能实现 
 	dp[0]=1;
//	for(int i=1;i<=amount;i++){
//		for(int j=0;j<coinsSize;j++)//遍历可用的每一个硬币,看是否能使用
//			if(i>=coins[j])
//				dp[i]+=dp[i-coins[j]];
//	}
	for(int i=0;i<coinsSize;i++){//遍历可用的每一个硬币,看是否能使用
		for(int j=0;j<=amount;j++)
			if(j>=coins[i])
				dp[j]+=dp[j-coins[i]];
	}
 
	return dp[amount]; 
}
int main(){
	int n;
	int coins[12],amount;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>coins[i];
	}
	cin>>amount;
	cout<<coinChange(coins,n,amount); 
	return 0;
} 

复盘
刚开始认为这道题与322除了dp[i]含义不同与状态方程不同没什么区别,直到手动运行上述注释的错误代码后发现那种写法有重复的方案。如coins[1 2 5],amount=5;在刚开始写的时候方案:“1 2 2”和方案“ 2 2 1”是都被记入到dp[i]中的,导致了很多重复。真佩服那种第一次就能想到避免重复的方法的人!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值