[路飞]_每天刷leetcode_03(使用最小花费爬楼梯)

201 阅读14分钟

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

使用最小花费爬楼梯

今天我们来看一道比较有意思的题目,使用最小花费爬楼梯问题,这个题有点小时候玩的大富翁游戏。具体的规则是什么呢,我们一起来看一看。

传送门 ===> leetcode: 使用最小花费爬楼梯

题目

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

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

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

You are given an integer array cost where cost[i] is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps.

You can either start from the step with index 0, or the step with index 1.

Return the minimum cost to reach the top of the floor.

Example:

Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
- Pay 15 and climb two steps to reach the top.
The total cost is 15.


Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
- Pay 1 and climb two steps to reach index 2.
- Pay 1 and climb two steps to reach index 4.
- Pay 1 and climb two steps to reach index 6.
- Pay 1 and climb one step to reach index 7.
- Pay 1 and climb two steps to reach index 9.
- Pay 1 and climb one step to reach the top.
The total cost is 6.

提示:

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

思考分界线


思路分析

看到这题我就懂了,这就是一道可以分而治之的题目。我立马来了思路

  1. 首先可以从0开始下脚起跳,也可以从1开始起跳。这样我们开始的点就有两个。若从0 开始起跳,我们的落脚点是1 || 2,若从1开始跳,我们的落脚点是 2 || 3.
  2. 随后我们可以根据脚步关系(每次起步走1~2 节楼梯)写出相应的递归函数。
  3. 循环递归,直到登顶。同时应注意,当最后登顶的时候要么是从倒数第二个位置上去的,要么是从最后一个位置上去的。

芜湖~,思路似乎很清晰,我懂了。于是信手写下了代码如下。

var minCostClimbingStairs = function (cost) {
  len = cost.length
  if (len == 0) return 0
  if (len == 1) return cost[0]
  if (len == 2) return Math.min(cost[0], cost[1])
  if (len == 3) return Math.min(cost[1], cost[0] + cost[2])
  return Math.min(
    cost[0] + recursion(cost.slice(1)),
    cost[0] + recursion(cost.slice(2)),
    cost[1] + recursion(cost.slice(2)),
    cost[1] + recursion(cost.slice(3))
  )
}
function recursion(cost) {
  len = cost.length
  if (len == 0) return 0
  if (len == 1) return cost[0]
  if (len == 2) return cost[0]
  return Math.min(cost[0] + recursion(cost.slice(1)), cost[0] + recursion(cost.slice(2)))
}
const cost = [10,15,20]
const cost1 = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]

console.log(minCostClimbingStairs(cost), minCostClimbingStairs(cost1)); // 15 6

经过测试,我们上面的测试用例都正常通过了。

嗯,很好,如此畅快的体验,我又觉得我行了。

那么接下来只剩下点击提交了,让我们看看我们的绿色通过图标。

可是,现实却是爆红了,提交结果为:超出时间限制。

失败的测试用例如下

const cost = [
  841, 462, 566, 398, 243, 248, 238, 650, 989, 576, 361, 126, 334, 729, 446,
  897, 953, 38, 195, 679, 65, 707, 196, 705, 569, 275, 259, 872, 630, 965, 978,
  109, 56, 523, 851, 887, 91, 544, 598, 963, 305, 481, 959, 560, 454, 883, 50,
  216, 732, 572, 511, 156, 177, 831, 122, 667, 548, 978, 771, 880, 922, 777,
  990, 498, 525, 317, 469, 151, 874, 202, 519, 139, 670, 341, 514, 469, 858,
  913, 94, 849, 839, 813, 664, 163, 3, 802, 21, 634, 944, 901, 446, 186, 843,
  742, 330, 610, 932, 614, 625, 169, 833, 4, 81, 55, 124, 294, 71, 24, 929, 534,
  621, 543, 417, 534, 427, 327, 179, 90, 341, 949, 368, 692, 646, 290, 488, 145,
  273, 617, 596, 82, 538, 751, 80, 616, 763, 826, 932, 184, 630, 478, 163, 925,
  259, 237, 839, 602, 60, 786, 603, 413, 816, 278, 4, 35, 243, 64, 631, 405, 23,
  638, 618, 829, 481, 877, 756, 482, 999, 973, 718, 157, 262, 752, 931, 882,
  741, 40, 77, 535, 542, 879, 607, 879, 321, 46, 210, 116, 244, 830, 591, 285,
  382, 925, 48, 497, 913, 203, 239, 696, 162, 623, 291, 525, 950, 27, 546, 293,
  108, 577, 672, 354, 256, 3, 671, 998, 22, 989, 557, 424, 251, 923, 542, 243,
  46, 488, 80, 374, 372, 334, 190, 817, 150, 742, 362, 196, 75, 193, 162, 645,
  859, 758, 433, 903, 199, 289, 175, 303, 475, 818, 213, 576, 181, 668, 243,
  297, 572, 549, 840, 161, 292, 719, 226, 338, 981, 345, 203, 655, 210, 65, 111,
  746, 76, 935, 406, 646, 976, 567, 32, 726, 638, 674, 727, 861, 426, 297, 349,
  464, 973, 341, 452, 826, 223, 805, 940, 458, 468, 967, 107, 345, 987, 553,
  407, 916, 103, 324, 367, 864, 74, 946, 712, 596, 105, 194, 79, 634, 855, 703,
  70, 170, 543, 208, 739, 632, 663, 880, 857, 824, 258, 743, 488, 659, 647, 470,
  958, 492, 211, 927, 356, 488, 744, 570, 143, 674, 502, 589, 270, 80, 6, 463,
  506, 556, 495, 713, 407, 229, 689, 280, 162, 454, 757, 565, 267, 575, 417,
  948, 607, 269, 852, 938, 560, 24, 222, 580, 604, 800, 628, 487, 485, 615, 796,
  384, 555, 226, 412, 445, 503, 810, 949, 966, 28, 768, 83, 213, 883, 963, 831,
  390, 951, 378, 497, 440, 780, 209, 734, 290, 96, 398, 146, 56, 445, 880, 910,
  858, 671, 164, 552, 686, 748, 738, 837, 556, 710, 787, 343, 137, 298, 685,
  909, 828, 499, 816, 538, 604, 652, 7, 272, 729, 529, 343, 443, 593, 992, 434,
  588, 936, 261, 873, 64, 177, 827, 172, 712, 628, 609, 328, 672, 376, 628, 441,
  9, 92, 525, 222, 654, 699, 134, 506, 934, 178, 270, 770, 994, 158, 653, 199,
  833, 802, 553, 399, 366, 818, 523, 447, 420, 957, 669, 267, 118, 535, 971,
  180, 469, 768, 184, 321, 712, 167, 867, 12, 660, 283, 813, 498, 192, 740, 696,
  421, 504, 795, 894, 724, 562, 234, 110, 88, 100, 408, 104, 864, 473, 59, 474,
  922, 759, 720, 69, 490, 540, 962, 461, 324, 453, 91, 173, 870, 470, 292, 394,
  771, 161, 777, 287, 560, 532, 339, 301, 90, 411, 387, 59, 67, 828, 775, 882,
  677, 9, 393, 128, 910, 630, 396, 77, 321, 642, 568, 817, 222, 902, 680, 596,
  359, 639, 189, 436, 648, 825, 46, 699, 967, 202, 954, 680, 251, 455, 420, 599,
  20, 894, 224, 47, 266, 644, 943, 808, 653, 563, 351, 709, 116, 849, 38, 870,
  852, 333, 829, 306, 881, 203, 660, 266, 540, 510, 748, 840, 821, 199, 250,
  253, 279, 672, 472, 707, 921, 582, 713, 900, 137, 70, 912, 51, 250, 188, 967,
  14, 608, 30, 541, 424, 813, 343, 297, 346, 27, 774, 549, 931, 141, 81, 120,
  342, 288, 332, 967, 768, 178, 230, 378, 800, 408, 272, 596, 560, 942, 612,
  910, 743, 461, 425, 878, 254, 929, 780, 641, 657, 279, 160, 184, 585, 651,
  204, 353, 454, 536, 185, 550, 428, 125, 889, 436, 906, 99, 942, 355, 666, 746,
  964, 936, 661, 515, 978, 492, 836, 468, 867, 422, 879, 92, 438, 802, 276, 805,
  832, 649, 572, 638, 43, 971, 974, 804, 66, 100, 792, 878, 469, 585, 254, 630,
  309, 172, 361, 906, 628, 219, 534, 617, 95, 190, 541, 93, 477, 933, 328, 984,
  117, 678, 746, 296, 232, 240, 532, 643, 901, 982, 342, 918, 884, 62, 68, 835,
  173, 493, 252, 382, 862, 672, 803, 803, 873, 24, 431, 580, 257, 457, 519, 388,
  218, 970, 691, 287, 486, 274, 942, 184, 817, 405, 575, 369, 591, 713, 158,
  264, 826, 870, 561, 450, 419, 606, 925, 710, 758, 151, 533, 405, 946, 285, 86,
  346, 685, 153, 834, 625, 745, 925, 281, 805, 99, 891, 122, 102, 874, 491, 64,
  277, 277, 840, 657, 443, 492, 880, 925, 65, 880, 393, 504, 736, 340, 64, 330,
  318, 703, 949, 950, 887, 956, 39, 595, 764, 176, 371, 215, 601, 435, 249, 86,
  761, 793, 201, 54, 189, 451, 179, 849, 760, 689, 539, 453, 450, 404, 852, 709,
  313, 529, 666, 545, 399, 808, 290, 848, 129, 352, 846, 2, 266, 777, 286, 22,
  898, 81, 299, 786, 949, 435, 434, 695, 298, 402, 532, 177, 399, 458, 528, 672,
  882, 90, 547, 690, 935, 424, 516, 390, 346, 702, 781, 644, 794, 420, 116, 24,
  919, 467, 543, 58, 938, 217, 502, 169, 457, 723, 122, 158, 188, 109, 868, 311,
  708, 8, 893, 853, 376, 359, 223, 654, 895, 877, 709, 940, 195, 323, 64, 51,
  807, 510, 170, 508, 155, 724, 784, 603, 67, 316, 217, 148, 972, 19, 658, 5,
  762, 618, 744, 534, 956, 703, 434, 302, 541, 997, 214, 429, 961, 648, 774,
  244, 684, 218, 49, 729, 990, 521, 948, 317, 847, 76, 566, 415, 874, 399, 613,
  816, 613, 467, 191,
]

嗯,cost的长度范围是[2, 1000]。这个也不是很大呀,为啥能超时呢。

我大概的估算一下,我们是递归调用,每次起步我们有2中方案,1000个楼梯我们大概有 2^1000种可能方案。好吧,这个数字的确不是很大嘛,只不过是100%劝退了我使用这种递归的方式而已。

既然这种递归方式由于极大地消耗内存和时间,我们不可取了,那么从递归变到迭代呢?

我进行了如下思考

  1. 我们的初始步伐为0或者1。
  2. 我们接下来第三节楼梯的最小步伐为 Math.min(cost[0],cost[1]) + cost[2]
  3. 同理,我们可以推断出第n节楼梯的最小步伐为Math.min(n-2的最小步伐数, n-1的最小步伐数)+cost[n]
  4. 我们可以用一个数组来维护每节楼梯的最小步伐数,我们定义该数组为let dp.
  5. 这样一直迭代到最后,可以算出0~cost.length-1的楼梯最小步伐数
  6. 而我们最后要求的结果是登顶的步伐数,所以我们可以用 Math.min(dp[len-1], dp[len-2])来求得结果。
  7. 这样我们的数据复杂度似乎只需要遍历一遍cost即可完成,为 O(n)
var minCostClimbingStairs = function(cost) {
    const dp = [cost[0], cost[1]];
    let len = cost.length
    for(let i = 2; i<len; i++) {
        dp[i] = Math.min(dp[i-1], dp[i-2]) + cost[i]
    }
    return Math.min(dp[len-1], dp[len-2])

};

执行测试用例,都通过,点击提交按钮,嗯,也很快得到了我向往已久的绿色通过图标。美妙~

好了,今天的刷题就到这里了,不过最后我又参考了其他人的代码,得到了一个更加普世的方案(虽然并没有什么性能提升),不过看起来更和谐了。代码如下:

var minCostClimbingStairs = function(cost) {
    const dp = [cost[0], cost[1]];
    cost.push(0);
  	cosnt len = cost.length
    for(let i = 2; i<=len;i++) {
        dp[i] = Math.min(dp[i-1], dp[i-2]) + cost[i]
    }
    return dp[len-1];
};

拜拜,我们明天见。