动态规划入门(一):从爬楼梯开始

动态规划是运筹学的一个概念,是用来解决多阶段决策过程相关问题的一种思想,一般简称为DP(Dynamic Programming),所谓多阶段决策过程是指有一个活动的过程可以分成若干个相互联系的阶段,在它的每一阶段都需要做出决策,不同的决策将影响活动最终的结果,这个过程如下图所示,活动的初始状态为状态0,经过一次决策后转移到状态1,这是一个中间结果,经过n次决策后转移到状态n,而状态n即为活动最终的结果。

与多阶段决策过程相关的问题大致有下面2种,它们是使用动态规划来解决的典型问题:

  1. 最优化问题:最优化问题要求每次做出的决策能够使得活动的最终结果达到最好。也可能有问题要求最终达到的结果最差,但本质上属于同一种问题。
  2. 决策方案数问题:决策方案数问题要求统计所有能成功到达最终状态的决策方案种数。

当然上述的描述十分之抽象,我们还是从下面一道简单的例题开始,去真正体会动态规划的思想:

例1.跳台阶

楼梯共有n(100 > n > 0)阶台阶,上楼时可以一步上1阶,也可以一步上2阶,试求出走到楼梯顶一共有多少种走法?

解动态规划的题,首先要确立状态,本题的初始状态为位于第0阶台阶,决策为走一级还是两级台阶,每做出一次决策转移到一个新的状态,即到达一个新的台阶,直到到达状态n,即第n级台阶。本题要求能够到达状态n的方案数,属于决策方案数问题。这里再介绍一个动态规划相关概念:无后效性原则,它是指某阶段的状态一旦确定,则在这一阶段以后过程的发展不受这阶段以前各个状态和决策的影响。由于无后效性的存在,对于状态n,我们只用考虑其最后一步是如何决策的,而不用考虑之前的状态。根据最后一步决策不同我们可以分为下面两种情况:

  • 走一级台阶:则此前的状态位于n-1级台阶。
  • 走二级台阶:则此前的状态位于n-2级台阶。

那么到达状态n的结果就等于到达状态n-1与n-2两种情况的结果之和。而对于n-1与n-2级台阶,也进行相同的分析,最终可以推广至任意第i级台阶。我们令f(i)表示到达第i阶台阶的走法数,由以上分析得:f(i) = f(i-1)+f(i-2) ,这是一个递推公式,在动态规划中我们将其称为状态转移方程。f(n)即为为最终的答案。

注意无后效性的前提是前面的状态已经确定,所以我们需要从初始状态开始依次向后计算,在程序中f(i)可以用递归的形式计算,但是递归会产生重复计算,可能会导致程序超时,可以用数组f[i]表示f(i),在递归时做一个记忆化,或者放弃使用递归直接用循环计算,那么在多数情况下我们都选择循环的方式,仅在部分类型的DP中使用记忆化的递归。

对于本题的初始状态,本来应该是位于第0级台阶,但是该状态的结果不太好确定,而位于第1、2级台阶的结果却能很容易求出来:第1级台阶只能是从第0级台阶走一步到达,f[1] = 1、第2级台阶可以从第0级台阶走两步到达,也可以先走一步到达第1级台阶,再走一步到达第2几台机,f[2] = 2,所以直接以位于第1级或第2级台阶作为初始状态计算。示例代码如下:

    public int jumpFloor(int n) {
        int[] f = new int[n+1];
        f[1]=1; f[2]=2;
        for(int i=3; i<=n; ++i){
            f[i] = f[i-1]+f[i-2];
        }
        return f[n];
    }

接下来我们在上面例题的基础上稍加修改,得到一个新的例题:

例2.最小花费爬楼梯

给定一个整数数组 cost ,其中 cost[i]  是从楼梯第i 个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯,请计算到达楼梯顶部的最低花费。

本题在例题1的基础上加入了代价,每次决策不再是免费的,而是需要支付费用,最终要求最低的花费,显然这题就是动态规划的另一个应用:最优化问题!本题的状态与例题1是一致的,决策也相同,区别在于不同的决策会带来不同的开销。模仿例题1的分析过程,考虑在第i级台阶的情况,同样只需考虑其前一个状态,其有两种不同的决策:

  • 走一级台阶:则此前的状态位于n-1级台阶。
  • 走二级台阶:则此前的状态位于n-2级台阶。 

令f(i)表示到达第i级台阶的最小花费,但这次f(i)不再是等于f(i-1)+f(i-2)了,因为我们不再是统计所有的方案数,而是找出一个最优的方案,所以在两种决策中,我们要找到更好的那个,本题中自然是花费越少越好。f(i-1)表示到达第i-1级台阶的最小花费,而从i-1到 i,需要花费cost(i-1) ,总花费为f(i-1)+cost(i-1),而f(i-2)表示到达第i-2级台阶的最小花费,从i-2到 i,需要花费cost(i-2),总花费为f(i-2)+cost(i-2),那么最优方案就是两种决策中花费更小的那个,由此我们得到状态转移方程f(i)=min{f(i-1)+cost(i-1), f(i-2)+cost(i-2)},同样用数组f[i]表示,循环计算递推式,初始条件题中已给出:可以选择从下标为 0 或 1 的台阶开始爬楼梯,即f[0]=0、f[1]=0,示例代码如下:

    public int minCostClimbingStairs (int[] cost) {
        int[] f = new int[cost.length+1];
        f[0]=0; f[1]=0;
        for(int i=2;i<=cost.length;++i){
            f[i]=Math.min(f[i-1]+cost[i-1],f[i-2]+cost[i-2]);
        }
        return f[cost.length];
    }

最后做一个总结,对于动态规划的题目,我们的解题思路是:

  1. 确定题目类型是最优化问题还是决策方案数问题;
  2. 从题干中提取有效信息,表示过程的状态
  3. 找到所有可用的决策;
  4. 跟据无后效性原则对任意状态i分析其前一阶段的决策;
  5. 设计出状态转移方程,即递推式
  6. 确定初始条件的结果;
  7. 确定递推顺序,计算递推式。

借助上面学到的动态规划知识,可以试着解决下面这两道题(点击题目即可跳转到对应页面),它们与例题是类似的,在做题的过程中请仔细思考状态的表示与状态转移方程的设计,这是动态规划问题的关键:

1.不同路径的数目(同例1)

题目类型:决策方案数问题

状态表示:f[i][j]表示到达矩阵(i,j)位置的路径数

决策:向下走或向右走,对应在矩阵中为i+1或i,j+1

对当前状态分析其前一阶段的决策:对于状态f[i][j],若它的前一阶段的决策为向下走,则它由f[i-1][j]转移而来;若它的前一阶段的决策为向右走,则它由f[i][j-1]转移而来。

状态转移方程:f[i][j]=f[i-1][j]+f[i][j-1]

初始状态:f[i][0]=1(在矩阵第一列只能由上面一格向下转移而来),f[0][j]=1(在矩阵第一行只能由左边一格向右转移而来)

    public int uniquePaths (int m, int n) {
        int[][] f = new int[m][n];
        for(int i=0;i<m;++i){
            f[i][0]=1;
        }
        for(int j=1;j<n;++j){
            f[0][j]=1;
        }
        for(int i=1;i<m;++i){
            for(int j=1;j<n;++j){
                f[i][j]=f[i-1][j]+f[i][j-1];
            }
        }
        return f[m-1][n-1];
    }

2.矩阵的最小路径和 (同例2)

题目类型:最优化问题

状态表示:f[i][j]表示到达矩阵(i,j)位置的最小路径

决策:向下走或向右走,对应在矩阵中为i+1或i,j+1

对当前状态分析其前一阶段的决策:对于状态f[i][j],若它的前一阶段的决策为向下走,则它由f[i-1][j]转移而来;若它的前一阶段的决策为向右走,则它由f[i][j-1]转移而来。

状态转移方程:f[i][j]=min(f[i-1][j], f[i][j-1])+matrix[i][j]

初始状态:f[i][0]=f[i-1][0]+matrix[i][0](在矩阵第一列只能由上面一格向下转移而来),f[0][j]=f[0][j-1]++matrix[0][j](在矩阵第一行只能由左边一格向右转移而来)

    public int minPathSum (int[][] matrix) {
        int m=matrix.length, n=matrix[0].length;
        int[][] f = new int[m][n];
        f[0][0]=matrix[0][0];
        for(int i=1;i<m;++i)
            f[i][0]=matrix[i][0]+f[i-1][0];
        for(int j=1;j<n;++j)
            f[0][j]=matrix[0][j]+f[0][j-1];
        for(int i=1;i<m;++i){
            for(int j=1;j<n;++j){
                f[i][j]=Math.min(f[i-1][j], f[i][j-1])+matrix[i][j];
            }
        }
        return f[m-1][n-1];
    }
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值