动态规划学习(一)

动态规划的概念有很多,我就不介绍了,我们直接在解题过程中体会什么是动态规划。
如题,有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。
比如,每次走1级台阶,一共走10步,这是其中一种走法。我们可以简写成 1,1,1,1,1,1,1,1,1,1。
再比如,每次走2级台阶,一共走5步,这是另一种走法。我们可以简写成 2,2,2,2,2。
要求用程序来求出一共有多少种走法。

当然,可以用排列组合的方式求解,但属于暴力枚举,时间复杂度时指数级的,这显然不是我们想要的。

假设只差一步就走到第10级,因为每次只能走 1 级或 2 级台阶,所以这时有两种情况:

  1. 从 9 级走到 10 级
  2. 从 8 级走到 10 级

那么就有了一个新的问题,如果知道从 0 级走到 8 级有X种走法,从 0 级走到 9 级有Y种走法,那么从 0 级到 10 级就有 X+Y 种走法。即所以走法根据最后一步而分为列举的两种情况,画出来就如下图:
这里写图片描述

现在得出一个结论:从 0 级到 10 级的走法 = 从 0 级到 9 级的走法 + 从 0 级到 8 级的走法;把10级台阶的走法写成f(10).
所以if(10) = f(9) + f(8);
按照刚才的思路不难想到 f(9) = f(8) + f(7) , f(8) = f(7) + f(6) …..
当只有一级或两级台阶时,显然只有一种或两种走法。所以f(1) =1,f(2) = 2;
可以归纳成以下公式:
f(1) =1 ;
f(2) = 2;
f(n) = f(n-1) + f(n-2);
问题分析到这里,就可以动手写代码了;

方法一,递归

从归纳的公式可以看出,这显然可以用递归调用来解决,所以

private static int getNumStep(int i) {
        if (i < 1) {
            return 0;
        }
        if (i == 1) {
            return 1;
        }
        if (i == 2) {
            return 2;
        }
        return getNumStep(i - 1) + getNumStep(i - 2);
    }

看上去挺简单,但是计算一下他的时间复杂度:要算f(n),就要算f(n-1)和f(n-2),要算f(n-1)就要算f(n-2)和f(n-3),以此类推,时间复杂度就为一棵二叉树,高度为n-1,所以为O(2^n).
这里写图片描述
但是从图中可以看出,有很多都是被重复计算过的,越往下走,重复的越多。
那么,为了避免这种重复,可以用一个Hashmap来记录下算过的值。所以有了第二种方法。

方法二 备忘录算法

private static int getNumStep(int i,HashMap<Integer,Integer> map) {
        if (i < 1) {
            return 0;
        }
        if (i == 1) {
            return 1;
        }
        if (i == 2) {
            return 2;
        }
        if (map.containsKey(i)) {
            return map.get(i);
        }else {
            int value = getNumStep(i - 1) + getNumStep(i - 2);
            map.put(i, value);
            return value;
        }

    }

集合map就是一个备忘录,当计算f(n)时,先查看map中是否含有这个值,有就直接返回该值,否,就计算出结果,保存在map中,然后返回。
该方法的时间和空间复杂度都是O(n)。但是我们还能不能更简化呢?

方法三 动态规划

前面的方法都是自顶向下递归计算,先算f(10),再算f(9)。那么换一种思路,可不可以自底向上迭代计算呢?
f(1)=1,f(2)=2,这是已经知道的,第一次迭代计算f(3)时,结果为 f(2)+ f(1),所以f(3)只依赖f(1)和f(2);同理第二次迭代计算f(4)时,f(4)只依赖f(3)和f(2)。
这里写图片描述
所以每次迭代只需要知道前两个状态就行了,所以代码为:

private static int getNumStep(int i) {
        if (i < 1) {
            return 0;
        }
        if (i == 1) {
            return 1;
        }
        if (i == 2) {
            return 2;
        }
        int a = 1;
        int b = 2;
        int value = 0;
        for (int j = 3; j <= i; j++) {
            value = a + b;
            a = b;
            b = value;
        }

        return value;
    }

程序从 j=3 开始迭代,一直到 j=n 结束。每一次迭代,都会计算出多一级台阶的走法数量。迭代过程中只需保留两个临时变量a和b,分别代表了上一次和上上次迭代的结果。 为了便于理解,我引入了temp变量。temp代表了当前迭代的结果值。
该方法的时间复杂度和空间复杂度分别为o(n)和o(1)。

总结

回顾解题过程,我们把从0级到10级的问题,分为从8级到10级和从9级到10级两种情况,再可以把从0级到9级分为从7级到9级和从8级到9级两种情况……以此类推,然后当台阶只有1级或两级的情况,走法显然只有1种或2种,这种把复杂问题分阶段简单化,把原问题分为若干子问题,子问题与原问题形式相同或类似(分治法将分解后的子问题看成是相互独立的),但是规模变小了,求出子问题的解,原问题的解就求出的思想,就是动态规划。
动态规划有三个重要概念,【最优子结构】、【边界】和【状态转移公式】。我们分析到 f(10) = f(9) + f(8),f(9)和f(8)就是f(10)的最优子结构,f(1) =1,f(2) = 2,就是问题的边界,如果一个问题没有边界,那么就无法得到一个有限的解。f(n) = f(n-1) + f(n-2);就是问题的状态转移公式。
当然,走台阶是动态规划中最简单的问题,因为它只有一个维度,要想熟练运用动态规划,还是得更多复杂的题。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值