JS使用最小花费爬楼梯

266 阅读4分钟

红茶配代码也不错

正题

数组的每个下标作为一个阶梯,第 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 。

最开始想到的方法:遍历数组,每一次能够前进 1-2 步,要求出数组结束时元素相加值最小,那简单呀,直接就从第一步开始,预判一下下一步的操作,可能走一步或者走2步,取元素值最小的那一步,然后将各步相加?

于是有了下面的代码:

var minCostClimbingStairs = function(cost) {
    let res = 0
    let current = -1
    while(current < cost.length - 1) {
        const next = cost[current + 1]
        const next2 = cost[current + 2]
        if (next2 === undefined) {
            break
        }
        if (next < next2) {
            res += next
            current++
        } else {
            current += 2
            res += next2
        }
    }
    return res
};

通过 current 指针表示当前走到了哪一步,当指针达到倒数第二个元素的时候停止遍历(因为预判下一步可以直接结束爬楼梯的过程了),每一次指针移动之前预判下一步是走一步还是走两步,即获取 current + 1 和 current + 2 对应的数值,取最小值,最终相加起来.

感觉大方向没毛病,但是结果似乎不对,就拿 示例1的测试用例来说: [10,15,20]

第一步预判可能是 10 或 15,那么取最小,可不就是 10嘛,然后再预判下一步, 15 或 20,取最小15,那么结果就是 10 + 15 = 25,但是肉眼可见的正确路径应该是 直接跳15,然后结束,结果应该是 15啊!问题到底出现在了哪里??

很显然这样的思考不够全面,只考虑了进1或者进2的问题,然后忽略了数组遍历的进度,明明可以一步到位,为何需要两步相加?原因是因为 15 要大于 10 + 20 的结果,假设测试用例改为 [10,35,20],那么这样计算就没有问题。很显然这样的思考方式要考虑的情况比较多,所以果断舍弃这种方案。

那么我们可不可以计算出到达每一个节点的最小花费呢??然后最终取最后一个元素和倒数第二个元素的最小值,理论上就是整个过程的最小花费。

所以最终又回到了通用问题解决方案:动态规划。。。

其实本来不太想考虑动态规划,但这个方法实在太香了!

动态规划的问题之前文章中遇到的太多了,就不做图表示了。

假定一个 dpList[],元素为 cost 对应下标的最小花费。

前两个节点的最小花费不需要计算,因为直接可以走一步到达:

dpList[0] = cost[0]; dpList[1] = cost[1]

前两步的最小花费即为 cost 本身。

划重点:从第二步开始,当前节点的最小花费为 上一个 或 上上个 节点的最小花费的最小值 加上 本身 cost

for(let index = 2 ; index < cost.length ; index++) {
        dpList[index] = Math.min(dpList[index - 1], dpList[index - 2]) + cost[index]
    }

最终返回 倒数第一和倒数第二个节点的最小花费 (因为可以直接从倒数第一和倒数第二个节点完成)

return Math.min(dpList[dpList.length - 1], dpList[dpList.length - 2])

完整代码:

var minCostClimbingStairs = function(cost) {
    if (cost.length < 2) {
        return 0
    }
    const dpList = new Array(cost.length).fill(0)
    dpList[0] = cost[0]
    dpList[1] = cost[1]
    for(let index = 2 ; index < cost.length ; index++) {
        dpList[index] = Math.min(dpList[index - 1], dpList[index - 2]) + cost[index]
    }
    return Math.min(dpList[dpList.length - 1], dpList[dpList.length - 2])
}

image.png

方法未必最优,但一定最香。[狗头]