动态优化——经济学和管理学中的变分法和最优控制_一个题目帮你入门动态规划算法

3dc98e44e08d66b9f5462915f6dc7708.png

一、题目

如果有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。

要求用程序来求出一共有多少种走法。

如果每次走1级台阶,一共走10步,这是其中一种走法。我们可以简写成 1,1,1,1,1,1,1,1,1,1。

如果每次走2级台阶,一共走5步,这是另一种走法。我们可以简写成 2,2,2,2,2。

当然,除此之外,还有很多很多种走法。

二、问题建模

我们可以利用排列组合的思想进行暴力枚举,写一个多层嵌套循环遍历出所有的可能性。每遍历出一个组合,让计数器加1。这个方法的时间复杂度是指数级的,一般不推荐使用。

这里我们可以使用动态规划解决问题。

动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想。不仅可以用于编程领域,也可以用于管理学、经济学、生物学。

动态规划总结起来就是:大事化小,小事化了。

上面的题目,假设你只差最后一步就走到10级台阶,这时候会出现几种情况呢?2种,因为每一步只能走1级或者2级台阶,第一种就是从9级走到10级,第二种就是从8级走到10级。所以不管前面的过程,想要走到10级,最后一步必然是从8级或者9级开始。

我们接着分析,如果我们已知从0到9级台阶的走法有X种,0到8级台阶的走法有Y种,那么0到10级台阶的走法有多少种?

10级台阶的所有走法可以根据最后一步的不同而分成两部分,第一部分的最后一步是从9级到10级,这部分的走法数量和9级台阶的走法数量是相等的,也就是X。第二部分的最后一步是从8级到10级,这部分的走法数量和8级台阶的走法属性是相等的,也就是Y。这两部分相加,总的走法数量是X+Y。

思路如下:

39afb4bf6fef2591a3810c81515b5279.png

因此,得出结论:从0走到10级台阶的走法数量=从0走到8级的走法数量+从0走到9级的走法数量

我们把10级台阶的走法数量写成F(10),那么F(10)=F(9)+F(8),然后我们只有求出F(8)和F(9)即可。

根据前面的思路可以推断出:

F(9)=F(8)+F(7)

F(8)=F(7)+F(6)

上述过程,我们把一个复杂的问题分阶段简化,逐步简化成简单的问题,这就是动态规划。

当只有1级台阶和2级台阶的时候,分别有1和2种走法,因此我们可以归纳出下面的公式:

F(1)=1

F(2)=2

F(n)=F(n-1)+F(n-2) (n>=3)

动态规划的三个重要概念:最优结构、边界和状态转移公式。

最优子结构:前面F(8)和F(9)是F(10)的最优子结构。

边界:当只有1级或2级台阶时,可以直接得出结果,无需继续简化,称F(1)和F(2)是问题的边界。如果一个问题没有边界,将永远无法得到有限的结果。

状态转移公式:F(n)=F(n-1)+F(n-2)是阶段与阶段之间的状态转移方程。这是动态规划的核心,决定了问题的每一个阶段和下一个阶段的关系。

三、求解问题

前面这个部分叫问题建模,下面要进行比较麻烦的阶段:求解问题。

根据上面的公式,我们很容易地写出递归代码

int getClimbingWays(int n){  if(n<1){    return 0;  }    if(n == 1){    return 1;  }    if(n == 2){    return 2;  }  return getClimbingWays(n-1)+getClimbingWays(n-2)}

此种算法的时间复杂度计算如下图

99398a501f4ab479ed5230c463854d8e.png

是一颗二叉树,高度是N-1,节点个数接近2的N-1次方。所以时间复杂度近似地看做是O(2^N)。

上述实现方式虽然能够解决问题,但是算法效率比较低,根据上面的递归图,有很多相同的参数被重复计算了,越往下越多

fec38b47c6c78703a03c685a94b92a2e.png

如图所示,相同的颜色代表了方法被传入相同的参数。

我们可以采取缓存的方式将不同参数的计算结果存入哈希表来避免重复计算,这种算法又被称为备忘录算法。

int getClimbingWays(int n,HashMap maps){  if(n<1){    return 0;  }    if(n == 1){    return 1;  }    if(n == 2){    return 2;  }    if(maps.contains(n)){    return maps.get(n)  }else{    int value = getClimbingWays(n-1)+getClimbingWays(n-2)    maps.put(n,value);    return value;  }}

集合maps是一个备忘录,当每次需要计算F(N)的时候,会首先从maps中寻找匹配元素。如果maps中存在,就直接返回结果,如果maps中不存在,就计算出结果,存入备忘录中。

此算法的时间和空间复杂度都是O(N)

四、算法优化——真正的动态规划实现

以上还不能算是真正的动态规划实现,还有优化的空间。

我们可以把思路逆过来,之前是对F(N)自顶向下做递归运算,也可以自底向上用迭代的方式推导出结果。

b1e0369be6a65944b3ff092df2464008.png

F(1)=1,F(2)=2 我们已经知道这个结果。

那么F(3)依赖于F(1)和F(2),有F(3)=F(1)+F(2)=3

同理,F(4)依赖于F(3)和F(2),有F(4)=F(3)+F(2)=5

由此可见,每一次迭代过程中,只要保留前之前的两个状态,就可以推导出新的状态,而不需要像备忘录算法那样保留全部的子状态。

真正的动态规划求解:

int getClimbingWays(int n){  if(n<1){    return 0;  }    if(n == 1){    return 1;  }    if(n == 2){    return 2;  }    int a = 1;  int b = 2;  int temp = 0;    for (int i=3;i<=n;i++){    temp = a + b;    a = b;    b = temp;  }    return temp;}

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

此算法的时间复杂度是O(N),空间复杂度是O(1)。

这就是动态规划,利用简洁的自底向上的递推方式实现了时间和空间上的最优化。

这道上楼梯的题目是动态规划领域中最简单的问题,希望对你了解和入门动态规划有所帮助。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值