简单算法题我重拳出击 | leetcode.[746]使用最小花费爬楼梯-JAVA

389 阅读3分钟

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

原题

使用最小花费爬楼梯

数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值cost[i](下标从 0 开始)。

每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。

请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

  • cost的长度范围是 [2, 1000]
  • cost[i] 将会是一个整型数据,范围为[0, 999]

示例?1:

输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。

示例 2:

输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。

重拳出击

这题依然标签还是动态规划,所以还是按照基本套路来。

首先仔细看了看题目,咋一看觉得挺像爬楼梯 但是很重的一个变化是,它每个阶梯有体力消耗,所以我们进行计算时,需要判断每一步 消耗的能量,并且要得出最低消耗情况下的,到达终点的方法。

  • 边界条件

    根据题目,没有特别明显的边界判断,那就是需要在解题过程中去加了。

  • 子问题

    根据之前的经验,一开始我们可能会这样设置子问题:以到 n 阶所消耗的体力为dp[n]

    但是用草稿演算一下,会发现很难通过 dp[n - 1] 去推断出 dp[n] ,所以我们需要另寻他法。在看一遍题目,可以发现每次能走一个阶梯或者爬两个阶梯,然后我们再从顶部往起始去思考,可以根据最后爬一个阶梯或者爬两个阶梯,然后重新设置子问题,分情况:

    dp[n] [0] 为最后一阶走一步, dp[n] [1] 为最后一阶走两步

    然后因为题目规定了,最小有两个台阶,所以我们可以重新设置边界条件

    dp(n)={prices[1][0]=0,prices[1][1]=0 n=1prices[2][0]=cost[1],prices[2][1]=cost[0] n=2dp(n)=\left\{ \begin{aligned} prices[1][0]=0,prices[1][1]=0 & &\ n = 1 \\ prices[2][0]=cost[1],prices[2][1]=cost[0] & &\ n = 2 \\ \end{aligned} \right.
  • 方程

    根据上一条,可以得知,每走到下个阶梯都要判断两种情况下的走法的消耗,每次取最小那个,然后在加上走一阶或者两阶的消耗,于是得出如下:

    dp(n)={prices[1][0]=0,prices[1][1]=0 n=1prices[2][0]=cost[1],prices[2][1]=cost[0] n=2dp[n][0]=cost[n1]+Math.min(dp[n1][0],dp[n1][1]); n>2;走一阶dp[n][1]=cost[n2]+Math.min(dp[n2][0],dp[n2][1]); n>2;走两阶dp(n)=\left\{ \begin{aligned} prices[1][0]=0,prices[1][1]=0 & &\ n = 1 \\ prices[2][0]=cost[1],prices[2][1]=cost[0] & &\ n = 2 \\ dp[n][0] = cost[n - 1] + Math.min(dp[n - 1][0], dp[n - 1][1]); & &\ n >2 ;走一阶\\ dp[n][1] = cost[n - 2] + Math.min(dp[n - 2][0], dp[n - 2][1]); & &\ n >2 ;走两阶\\ \end{aligned} \right.

    当数组 cost[i] 遍历结束之后,会得到一个 dp[n] 数组中存的就是结果

  • 结合各种条件写代码

    public int minCostClimbingStairs(int[] cost) {
        //初始化二维数组
        int[][] dp = new int[cost.length + 1][2];
		//边界条件
        dp[1][0] = 0;
        dp[1][1] = 0;
        dp[2][0] = cost[1];
        dp[2][1] = cost[0];
        //根据转移方程变的代码
        for (int i = 3; i <= cost.length ; i++) {
            dp[i][0] = cost[i - 1] + Math.min(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = cost[i - 2] + Math.min(dp[i - 2][0], dp[i - 2][1]);
        }
        return Math.min(dp[cost.length][0], dp[cost.length][1]);
    }

然后运行,发现 执行耗时击败了99.84%,内存击败了97.12%

结果还不错,这个明显还可以空间优化,每个分支也有大量的重复计算,能够用几个变量去取代多个二维数组。由于懒了,于是就摸鱼🦑了。

  • 动态规划的子问题设置很重要,一个好的子问题可以极大的减少复杂度