使用最小花费爬楼梯(Min Cost Climbing Stairs) Java动态规划入门分析一

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/malimingwq/article/details/81273534

前言

首先做题不是目的,我写这篇文章的目的是通过例题来分析动态规划,揭开动态规划神秘面纱的的一角,我是想通过一些例题分析来带领我和大家入动态规划的门,若是我哪里分析或思考的有误区,或者错误,请大家能够对我进行评论或私信指正
1.使用最小花费爬楼梯(Min Cost Climbing Stairs) Java动态规划入门分析一
2.打家劫舍(House Robber)Java动态规划入门分析二
3.剪绳子(CutRope) Java动态规划入门分析三

题干

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

每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。

您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。

示例 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。
注意:

cost 的长度将会在 [2, 1000]。
每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。

动态规划-入门解析一(正常思维)

首先分析题干,题目的注意事项,假设数组b是存储值数组,a数组是原数组,首先 你可以选择从索引为 0 或 1 的元素作为初始阶梯 这句话分析得 b[0] = a[0],b[1] = a[1] ,接着分析数组a有最少的元素的时候为两个元素的时候:
f(2) = Math.min(b[0],b[1]);
然后分析数组a有三个元素的时候:
f(3) = Math.min(b[0]+a[2],b[1]+a[2]) = f(2)+a[2];
比如数组a为 [10,15,20],通过我们的大脑肯定一眼看出,答案为15,但是计算机需要有严谨的计算步骤:(Math.min(10 + 20, 15 + 20))= 30,此时数组b,为[10,15,30],我们还缺少一步,这个仅仅是b[0],b[1]做了对比就加a[2],我们实际上对比应该是b[i] 和 b[i+1] = Math.min(b[i - 2],b[i - 1]) +a[i]做对比。

代码

public static int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length];
        dp[0] = cost[0];//两个起点
        dp[1] = cost[1];
        for(int i = 2; i < cost.length; i++) {
            int costCurrent = cost[i];
            dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent;
        }
        return Math.min(dp[cost.length - 1],dp[cost.length-2]);
    }

动态规划-入门分析二(正常思维下的巧妙思想)

看了分析一后的代码,有没有发现有一些相同的地方,或者我们没考虑到的地方,或者更加巧妙的地方呢?观察:
return Math.min(dp[cost.length - 1],dp[cost.length-2])
dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent
它们区别除了 + costCurrent 也没什么区别,我们何不来统一一下,使得结构巧妙一些

代码二

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

评价一个算法的优劣应该从:正确性,可读性,健壮性,高效性(时间复杂度,空间复杂度),
所以我们应该在保证自己写的正确的情况下减小时间复杂度,空间复杂度。
首先分析原代码的时间复杂度和空间复杂度:
时间复杂度:找出所有语句中频度最大的那条语句作为基本语句,计算基本语句的频度得到的问题规模n的某个函数f(n) = a^m n^m+a^m-1 n^m-1+…+a n+a是一个m次多项式,则T(n) = O(n^m)
所以通过以上分析得,该算法时间复杂度为O(n);
空间复杂度:对一个算法在运行过程中临时占用存储空间大小的量度(辅助存储空间),记做S(n)=O(f(n)),所以通过以上分析空间复杂度为O(n);
然后我们在分析题目,此时我们已经不能再减小时间复杂度(必须有循环),我们可以考虑一下优化空间复杂度,通过分析 dp[i] = Math.min(dp[i - 1], dp[i - 2]) + costCurrent; 我们发现每一次迭代的过程中我们只要使用前两个的状态就可以得到新的状态,所以我们完全没有必要把值全部保留下来,每次保留前两个就可以,所以空间复杂度就降低到O(1)。

代码三

    public static int minCostClimbingStairs(int[] cost) {
        int sum = 0,sum1 = cost[0],sum2 = cost[1];
        for(int i = 2; i <= cost.length; i++) {
            int costCurrent = i == cost.length ? 0 : cost[i];
            sum = Math.min(sum1, sum2) + costCurrent;
            sum1 = sum2;
            sum2 = sum;
        }
        return sum;
    }

若是哪里有理解错误的或写错的地方,望各位读者评论或者私信指正,不胜感激。
题目网址:https://leetcode-cn.com/problems/min-cost-climbing-stairs/description/

猜你喜欢

转载自blog.csdn.net/malimingwq/article/details/81273534