用爬楼梯来认识动态规划

405 阅读4分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

啥是动态规划?

“动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。---来自百度百科

啥题目要用动态规划去求解?

我们一般是用动态规划来解决最优问题。比如最大序列合,最长/短路径,最佳时机等等,求最优问题我们就可以考虑是否用动态规划来解决。

如何用动态规划解题?

  • 确定递推状态
  • 确定状态转移方程
  • 确定边界条件
  • 实现算法

举个栗子?

力扣第746题 使用最小花费爬楼梯

image.png

从题目最小花费我们就可以看出,这题可以用动态规划去解。我们先画个图,然后来一步步按上述步骤解题:

image.png

  • 确定递推状态 f(x) = y;

y是我们求的值,即走到第i个台阶需要花费的体力。

那x是引起y变化的因素,因素有哪些?这道题只有台阶数是因变量,所以我们定义x为台阶数。

  • 确定状态转移方程

有题目和图示,我们可以看出,如何走到dp[i]?要么从倒数第一个台阶上去,花费为dp[i-1] + costs[i-1],要么从倒数第二个台阶上去,花费为dp[i-2]+costs[i-2]。而题目要求是最小花费,所以取两者的较小值就行。

所以我们可以定义状态转移方程为:

dp[i] = min(dp[i-1] + costs[i-1], dp[i-2] + costs[i-2])
  • 定义边界条件

我们根据状态转移方程可以知道,想知道第i个台阶的花费,就得知道上一个和上上一个台阶的花费,那边界条件就是第一阶和第二阶。而爬上第一阶和第二阶都不需要花费。(第一阶和第二阶都可以一次性跨上去,这里就不需要花费。我们只有从需要花费的台阶往上爬才需要消费costs[i],例如从第一阶或者第二阶往上爬)

    dp[0] = 0;
    dp[1] = 0;
  • 实现算法
    var minCostClimbingStairs = function(cost) {
    const n = cost.length;
    const dp = []
    dp[0] = 0;
    dp[1] = 0;
    for (let i = 2; i <= n; i++) {
        dp[i] = Math.min((dp[i - 1] + cost[i - 1]), (dp[i - 2] + cost[i - 2]))
    }
    return dp[n]
};

OK,下面来几道简单的动态规划题目

杨辉三角

image.png

这道题,f(x) = y。我们直接定义y是第i行的数据好像不太合适,这样y是个数组,我们想他是个准确的值。那我们就定义y是第i行第j位的数据,这样题目的求值就是第i行的所有数据了。由此可得状态转移方程为

    // dp[i][j]表示由上往下求第i行第j位的值
    dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

代码如下:

var getRow = function(rowIndex) {
    // 数组长度
    const n = rowIndex + 1;
    // 初始化二维数组,并初始化值为1,这样杨辉三角的边界条件就省略了
    const dp = new Array(n).fill(1).map(() => new Array(n).fill(1))
    
    for(let i = 1; i < n; i++) {
        for (let j = 1; j < i; j++) {
            dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
        }
    }

    return dp[n - 1]
};

打家劫舍

image.png

题目看着就刺激。。。学好算法再就业也可以利润最大化。。。

我们要求的是偷窃第i家房屋所得的最大金钱数,因为题目告诉我们不能连着偷,所以偷到第i家的最大金钱数是偷到第i-1家的最大金钱数偷到第i-2家的最大金钱数再加上第i家的金钱之和两者中取较大值。

所以我们直接定义状态转移方程dp[i] = max(dp[i-1], dp[i-2] + nums[i])

由状态转移方程可知,dp[i]只与dp[i-1]和dp[i-2]有关,所以我们可以只定义一个长度为3的数组,然后轮番替换值,节省空间。这种优化方法叫做滚动数组

代码如下:

var rob = function(nums) {
    const n = nums.length;
    if (n === 1) return nums[0]
    const dp = new Array(3)
    
    dp[0] = nums[0]
    dp[1] = Math.max(nums[0], nums[1])
    
    for (let i = 2; i < n; i++) {
        const idx = i % 2;
        const idx_pre = (i - 1) % 2;
        const idx_pre_pre = (i - 2) % 2
        dp[idx] = Math.max(dp[idx_pre], dp[idx_pre_pre]+nums[i])
    }
    return dp[(n - 1) % 2]
};

粉刷房子

image.png

刚™偷完房子就粉刷房子。。可能是出来劳改来了。。。

这道题就留给大家自己思考啦。随便列个状态转移方程留个言?上面的题目还有优化的空间,也希望大家可以留言讨论~等我再学两天,再出个动态规划2.0,3.0难度的题目解析。

image.png

😀😀😀欢迎大家讨论,看完记得点个👍🏻哦。本文的代码已放Github,还有其他动态规划的题目,没有写进文章,但是git里都有写。里面还有之前算法的代码,后续代码也会陆续更新,欢迎大家不吝赐教。