动态规划——硬币问题

来源:互联网 发布:千牛mac版使用 编辑:程序博客网 时间:2024/09/21 09:20

package dynamic_programming;

/**
* @author Tom Qian
* @email tomqianmaple@outlook.com
* @github https://github.com/bluemapleman
* @date 2017年8月22日
*/

/* 问题:我们有无数个面值为1、3、5的硬币,试问给定目标值n,如何用最少的1、3、5硬币组合出n值。
*
* 思路:
* 动态规划问题的解答分成两步:1、定义状态 2、找到状态转移方程
*
* 1、定义状态
* 状态描述的是子问题的解。而子问题顾名思义,是我们面对的原始问题的一个子问题,只要解决了这个子问题,那么原始问题也能迎刃而解。
* 在硬币问题中,原始问题是我们最少用多少个硬币能组合出n值,那么我们就往简单一点想:最少用多少个硬币能组合出n-1值呢?因为假设我们只有一元硬币时,我们要组合出n值,获得solutions(n),是不是只需要知道solutions(n-1)即可,因为solutions(n)=solutions(n-1)+1,只要再拿一枚一元硬币就ok。同样继续回推,solutions(n-1)=solutions(n-2)+1……
*
* (……..是不是有点递归的味道?而其实,动态规划就是一种优化的递归方式。大概可以先这样理解:递归需要重复对比较低层次的最优解进行计算,而动态规划把不断地把低级解给存储起来,以备后面求解更高层次的最优解时使用,因此一般动态规划的效率会比递归高一些)
*
* 你可能奇怪,这么简单的问题有什么好推的?
* 而关键在于,绝大多数时候,我们有不只一个选择(硬币不会只有一种面值的) ,比如下面的代码阐述的是硬币有1、3、5三种面值时所有对应的最少硬币数解,这种情况下,当你想用动态规划思路求solutions(n),你有三种选择:
* 1. 拿一枚1元硬币:choice1=solutions(n-1)(组合出n-1值的最少硬币数)+1(1枚1元硬币)
* 2. 拿一枚3元硬币:choice2=solutions(n-3)(组合出n-3值的最少硬币数)+1(1枚3元硬币)
* 3 拿一枚5元硬币:choice3=solutions(n-5)(组合出n-5值的最少硬币数)+1(1枚5元硬币)
* 这三种和硬币种类一一对应的选择,就是三种状态/子问题的解。而接下来我们需要在三个解中作出一个抉择–>选择值最小的那个解,即硬币数最少的情况,那么显然,可以表达为:solutions(n)=min(choice1,choice2,choice3).而这样也就引出了下面一个问题:在不同状态/子问题的解中抉择的依据,即我们需要定义的状态转移方程。
* 上述的是一个一般性的情况,而这样不断回推,显然我们的问题会被推演到最初始最简单的一些问题的解,而它们是解决上一段中那些高层次问题解的基础:当我们有1、3、5三种硬币时,我们需要多少个硬币组合出0值->0个(不需要拿硬币出来),1值->1个,2值->2个。这三个解拍拍脑袋直接想出来,因为你只有用1元硬币去组合这一种选择,可是当面对n=3时,你有两个选择,1.再拿一枚1元硬币 2.丢掉手里两枚1元硬币,拿一枚3元硬币 显然后者是目标解。即此时,如上一段所属,因为硬币的可选数超过了1,我们开始需要做解的抉择,即必须用到状态转移方程了!
*
* 2、找到状态转移方程
* 当我们面对两种状态(解)的选择时,根据我们的问题,我们必须从中做出抉择。显然,这里我们要找最少的硬币数,那么当我们面对n=3时,我们需要的解即为:
*
* min(solution(3-1)+1,solution(3-3)+1)
*
* 显然,拿一枚面值3的硬币就行了,而我们在这两个解之间抉择出一个目标解的过程即为状态转移方程(此处是找出最小值)。
*
* 那么总结一下:解硬币问题的过程就变成了这样:
*
* 我们从最简单的状态开始,目标值n=1,n=2,n=3….,而解出简单状态的过程需要我们用这样的逻辑去处理:
* 当n==0时,直接把解solutions[0]置为0(不需要拿出硬币)
* 当n>0时,看有无面值比当前n小或相等的硬币?
* - 没有的话,无解,solutions[n]=Integer.MAX_VALUE。
* - 有的话,比较min(solution(n-vj)+1) j=1,2,…,J所有面值硬币数,即把所有可能的硬币选择解全部做比较,选出最小的那个。
*/

public class 硬币问题{    public static int leastCoins(int goal,int []coins){        if(goal==0)            return 0;    int [] solutions=new int[goal+1];    for(int i=0;i<solutions.length;i++)        solutions[i]=Integer.MAX_VALUE-10000;    solutions[0]=0;    for(int i=coins[0];i<=goal;i++){        for(int j=0;j<coins.length;j++){            if(coins[j]<=i && solutions[i-coins[j]]+1<solutions[i])                solutions[i]=solutions[i-coins[j]]+1;        }    }    return solutions[solutions.length-1];}public static void main(String[] args){    int[] coins={1,3,5};    for(int i=0;i<10;i++){        System.out.println(leastCoins(i, coins));    }}}