动态规划

1.概念

2.个人理解

个人感觉这是一种由最简单的状态推得复杂的状态的算法,相较于编程更是一种数学思想,用来解决存在相同子问题的时候求最优,缓存前一个状态的结果,避免重复计算。

3.硬币找零应用动态规划

假设有几种硬币,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。

硬币数量无穷大,硬币种类未知,求最少,存在找零时多种硬币面值使用与否 看上去像是动态规划可以解决的问题

抛开算法的概念,用数学的思想列出来一些结果找规律,假设硬币面值为2,3,5

列出表

 表内容每一项为硬币数量

硬币面值/找零金额   0    1    2    3    4    5    6    7    8    9    10
2 INF INF 1 INF 2 INF 3 INF 4 INF 5
3 INF INF 1 1 2 2 2 3 3 3 4
5 INF INF 1 1 2 1 2 2 2 3 2
INF表示找不到

去掉表内重复内容

硬币面值/找零金额   0    1    2    3    4    5    6    7    8    9    10
只使用2找零       INF  INF   1   INF   2   INF   3   INF   4   INF   5
使用2,3找零                       1    2    2    2    3    3    3    4
使用2,3,5找零                               1    2    2    2    3    2 
INF表示找不到

单从这十项可以得到这样的一个结论:

当使用的最大硬币面值小于找零面值时,

其找零所需的硬币数与不使用这种硬币找零所需的硬币数相同,因为该种硬币太大,比如使用2,3面值的硬币给4找零时,使用面值为3的硬币不能给面值为4的钱换零,只能用两个面值为2的硬币找零。

同时,如果找零使用的最大硬币面值大于等于找零金额,其所使用的硬币总数总是小于等于不使用此种硬币时的数量。从表中的数据得出其所使用的硬币数量为 (找零金额-最大硬币面值)所需的硬币数量+1(最大面值硬币数量)。

如果用数组按从小到大的顺序进行存储的话得到:

int[] coins = new int[]{2,3,5};

 可以列出这样的一个方程:

当j<coins[i]      f(i,j)=f(i-1,j);                                              

当j>=coins[i]    f(i,j)=Integer.min(1+f(i,j-coin[i]),f(i-1,j));   由于存在找零金额为4,硬币面值使用2,3时 使用一张3面值的硬币进行找零,余一个值为1的面值,没有硬币面值能兑现这个余数的情况,所以还要与使用2对4进行找零的情况进行比较得出合理的结果

 i表示使用的最大硬币面值,j表示找零金额

由于每增加使用一种硬币,存在重复的硬币数量和不符合要求的数量

如表中找零金额为6的列

硬币面值/找零金额  6
只使用2找零       3
使用2,3找零 2

只使用硬币面值为2的进行找零时使用3个硬币

使用2,3找零时使用2个硬币

求最少,需要用2替换掉3

可以用一个一维数组存储,从找零金额为0推算出只使用最小面值的硬币时所需的硬币数

方便视觉效果再贴上上面的数据

硬币面值/找零金额   0    1    2    3    4    5    6    7    8    9    10
只使用2找零       INF  INF   1   INF   2   INF   3   INF   4   INF   5
使用2,3找零                       1    2    2    2    3    3    3    4
使用2,3,5找零                               1    2    2    2    3    2 
INF表示找不到
硬币面值/找零金额   0    1    2    3    4    5    6    7    8    9    10
只使用2找零       INF  INF   1   INF   2   INF   3   INF   4   INF   5

伪代码:定义数组int[] dp = new int[11]{INF,INF,1,INF,2,INF,3,INF,4,INF,5};

当加入硬币面值为3时

硬币面值/找零金额   0    1    2    3    4    5    6    7    8    9    10
使用2,3找零                       1    2    2    2    3    3    3    4

替换掉dp中较大的值

dp = {INF,INF,1,1,2,2,2,3,3,3,4}

方程可以修改为:                                             

当j>=coins[i]    f(j)=Integer.min(1+f(j-coin[i]),f(j));   由于存在找零金额为4,硬币面值使用2,3时 使用一张3面值的硬币进行找零,余一个值为1的面值,没有硬币面值能兑现这个余数的情况,所以还要与使用2对4进行找零的情况进行比较得出合理的结果

 此时,动态规划的三大分析条件

1.最优子结构  我未分析出

2.边界 0

3.状态转移方程 为上面的修改后方程

 

4.我的Java代码实现

    /**
     * @param coins 零钱面值数组
     * @param n     需要被找零的钱
     */
    public int coinForChange(int[] coins, int n) {
        //排序从小到大
        Arrays.sort(coins);
        //找零的钱比最小零钱都小 找不开
        if (n < coins[0]) {
            return -1;
        }
        int coinsKind = coins.length;
        //定义第一种零钱面值 0-n面额下找零硬币数量数组
        int[] dp = new int[n + 1];
        //余数 标记只使用最小面值硬币是否可以找零
        int remainder;
        int value;
        //初始化找零数量 0-n计算找零所需硬币 找不开使用Integer的最大值表示 即上表中INF
        for (int i = 0; i < dp.length; i++) {
            remainder = i % coins[0];
            //找不开时 使用找零钱币为最大表示
            dp[i] = remainder == 0 ? i / coins[0] : Integer.MAX_VALUE;
        }
        //从加入第二种零钱面值 开始循环到第i种零钱面值
        for (int i = 1; i < coinsKind; i++) {
            //加入的该种零钱金额比找零的总额大的,该找零钱币数量与上一次循环得到的结果相同
            for (int j = coins[i]; j < dp.length; j++) {
                value = 1 + dp[j - coins[i]];
                //若最小面值不能找开零钱时为2^31 - 1
                //+1后int型溢出为负数 此时扔找不开 重新赋值为最大
                value = value >= 0 ? value : Integer.MAX_VALUE;
                dp[j] = Integer.min(value, dp[j]);
            }
        }
        return dp[n];
    }

最近在刷leetcode学习算法,经常遇到动态规划解决的问题,之前忽略了刷leetcode的目的,为刷题而刷题,每次遇到都需要花功夫理解程序,这几天忽然醒悟,研究算法解决问题,理解还是很浅,如果有错误或模糊不清的地方请各位大佬指教。

posted on 2019-05-17 10:01  不爱吃蘑菇的大蘑菇  阅读(225)  评论(0编辑  收藏  举报