最少硬币问题

1.题目

        钱包里有一些硬币,1元,5元,10元,50元,100元,500元,现在用这些硬币去买自动贩卖机里价格为A的饮料。假设自动贩卖机所需金额必须是刚刚好,不能多不能少。问最少需要多少枚硬币
        输入:
        1元,5元,10元,50元,100元,500元每枚硬币的个数 和 A的值
        输出:
        凑成A的最少硬币数或者NOWAY

2.动规法解题

       思路先将当前的面额大的硬币尽量多用,当超过所规定的硬币数量时,去寻找之前硬币所能组成的组合。
       
       子问题划分:二维备忘录dp[i][j] 代表前i种硬币凑成当前金额j所需的最少个数

       辅助数组:curCoinNum[i][j]记录第i种硬币在金额为j时用了多少个  
       
       初值   
       其中coinNum[i]是第i中硬币的个数
           递推方程
        其中coins是6种硬币的面额
        

       代码:
	static int[] coins = {-1,1,5,10,50,100,500};
	static int n = coins.length-1;
	static int[] coinNum = new int[n+1];
	static int A;
	static int MAX = Integer.MAX_VALUE;
	
	static String DP(){
		//curCoinNum[i][j]代表 第i个硬币在价值为j的时候用了几个
		int[][] curCoinNum = new int[n+1][A+1];
		//动归求解,dp[i][j]表示前i个硬币对于价值j时所需的最小数量
		int[][] dp = new int[n+1][A+1];
		//初值
		for(int i=1;i<=n;i++)
			for(int j=1;j<=A;j++)
				dp[i][j] = MAX;
		
		//初值 ,当前价值只用第一种硬币组成
		for(int j=1;j<=A;j++)
			if(j <= coinNum[1])
				dp[1][j] = j;
			
		for(int i=2;i<=n;i++){
			for(int j=1;j<=A;j++){
				if(coinNum[i]>0 && j>=coins[i]){
					//尽量先将当前硬币用光
					if(curCoinNum[i][j-coins[i]]+1 <= coinNum[i]){
						//若上一个硬币也无法达到j-coins[i]的价值,则当前硬币也无法达到
						dp[i][j] = dp[i][j-coins[i]]==MAX?MAX:dp[i][j-coins[i]]+1;
						curCoinNum[i][j] = curCoinNum[i][j-coins[i]] + 1;
					}else{
						//当前硬币已用尽时
						int min = dp[i-1][j];
						
						for(int k=1;k<i;k++){
							if(dp[k][j-coins[i]]<MAX && min>(dp[k][j-coins[i]]+1)){
								min = dp[k][j-coins[i]]+1;
							}
						}
						if(min < Integer.MAX_VALUE)
							curCoinNum[i][j] = 1;	//若可凑成当前金额,则代表只用了一个当前硬币
						dp[i][j] = min;
						
					}
				}else
					dp[i][j] = dp[i-1][j];
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=A;j++){
				System.out.print(curCoinNum[i][j] + " ");
			}
			System.out.println();
		}
			
		if(dp[n][A] == MAX)
			return "NOWAY";
		return dp[n][A]+"";
		
	}

	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] input = br.readLine().split(" ");

		for(int i=1;i<=6;i++){
			coinNum[i] = Integer.parseInt(input[i-1]);
		}
		A = Integer.parseInt(input[6]);

		System.out.println( DP());
	}

3. 回溯法解题

        思路:此题可转化成为0-1背包问题(将每种硬币乘以其数量),这样一来我们只用考虑取值是1的硬币的个数
          输入:n个硬币(面值可相同)

        解空间:n+1层高的满二叉树(左1右0)

        可行解:A为总金额
                    

          最优解:众多可行解中取值为1的硬币最少的解


       代码:

        

	static int[] coin = {1,5,10,50,100,500};
	static List<Integer> coins;
	static int A,	//初始给定金额
			   n,	//总硬币数
			   x[],	//记录当前硬币的0,1取值
			   bestx[],//记录最优解的0,1取值
			   cv,	//当前目标金额
			   bestn, //最少硬币个数
			   cn;	//当前硬币个数 
	
	static void Backtrack(int i){
		if(i>=n){
			if(cv==0){
				if(bestn > cn){
					for(int j=0;j<n;j++)
						bestx[j] = x[j];
					bestn = cn;
				}
			}
			return;	
		}
		if(cv >= coins.get(i)){
			x[i] = 1;
			cv -= coins.get(i);
			cn += 1;
			Backtrack(i+1);
			cn -= 1;
			cv += coins.get(i);
		}
		Backtrack(i+1);
	}
	
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] str = br.readLine().split(" ");
		A = Integer.parseInt(str[str.length-1]);
		cv = A;
		coins = new ArrayList<Integer>();	//将所有硬币化为单个对象,放入集合之中
		bestn = Integer.MAX_VALUE;
		
		for(int i=str.length-2;i>=0;i--){
			int num = Integer.parseInt(str[i]);
			for(int j=0;j<num;j++){
				coins.add(coin[i]);
			}
		}
		n = coins.size();
		x = new int[n];
		bestx = new int[n];
//		for(int i=0;i<coins.size();i++)
//			System.out.print(coins.get(i)+" ");
		
		Backtrack(0);
		System.out.println(bestn==Integer.MAX_VALUE?"NOWAY":bestn);
		for(int i=0;i<n;i++){
			if(bestx[i] != 0)
			System.out.println(coins.get(i));
		}
	}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值