Problem overview
The method minCostClimbingStairs(cost) receives an array where cost[i] is the price of standing on step i. After paying a step's price you may climb one or two steps. You can start on step 0 or on step 1, and the goal is the top of the staircase, which is one position past the last step. The method returns the cheapest total price to get there.
For cost = [10, 15, 20] the answer is 15: start on step 1, pay 15, and jump two steps to the top. For cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] the answer is 6. Start on step 0 and use only the cheap steps, skipping the 100s by taking two-step jumps.
Approach: the cheapest way to each step
Trying every sequence of one-step and two-step moves means a number of paths that grows like the Fibonacci numbers. Dynamic programming avoids repeating work by asking one question per step: what is the cheapest total price to arrive on step i and pay for it?
You can only get to step i from step i - 1 or step i - 2, so the answer is the step's own price plus the cheaper of those two totals. Steps 0 and 1 are free starting points, so their totals are just their own prices.
The top is reached from either of the last two steps, without paying for anything beyond them. The result is therefore the smaller of the final two totals.
The lesson stores the totals in cost itself. Each entry is read two steps later and then no longer needed in its original form, so no second array is required. For [10, 15, 20], step 2 becomes 20 + min(15, 10) = 30. The last two totals are 15 and 30, and the answer is 15.
Step by step
- Loop from index 2. The first two entries already hold the right totals, so the loop begins at the third step.
- Add the cheaper predecessor. For each index, add
Math.min(cost[i - 1], cost[i - 2])tocost[i]. Afterward the entry means "cheapest total to stand on this step", not the step's own price. - Take the smaller of the last two totals. Return
Math.min(cost[cost.length - 1], cost[cost.length - 2]).
The solution in TypeScript
class Solution {
minCostClimbingStairs(cost: number[]): number {
for (let i = 2; i < cost.length; ++i) {
cost[i] += Math.min(cost[i - 1], cost[i - 2]);
}
return Math.min(cost[cost.length - 1], cost[cost.length - 2]);
}
}
Some TypeScript details:
- The input array is changed. Arrays are passed by reference, so the caller's array holds totals afterward. Running the method on
[10, 15, 20]leaves that array as[10, 15, 30]. If you need the original prices, copy first with[...cost]. +=withMath.min.cost[i] += xmeanscost[i] = cost[i] + x.Math.minaccepts any number of arguments and returns anumber.- Index reads outside the array. Reading
cost[-1]orcost[cost.length]doesn't throw; it givesundefined, andMath.minof that isNaN. The code avoids this by starting at index 2 and relying on the length of at least 2 that the notes promise. - Numbers are doubles. The largest total here is about 1000 * 999, far below any limit, so there is no overflow.
Complexity
Time: O(n). One pass, constant work per step.
Space: O(1) extra. The totals reuse the input array, so nothing new is allocated. If the input must stay unchanged, a copy brings this back to O(n).
Edge cases and common mistakes
- Two steps. The loop doesn't run and the answer is the smaller price.
[5, 3]returns 3 and[0, 0]returns 0. - Returning only the last total. The top can be reached from the second-to-last step, so
[10, 15, 20]would give 30 instead of 15. - Starting the loop at 0 or 1. Then
cost[i - 2]is outside the array. The result turns intoNaNrather than a crash. - Forgetting the step's own price. Replacing
+=with=would throw away the price you pay on each step.
Testing
[10, 15, 20]gives15.[1, 100, 1, 1, 1, 100, 1, 1, 100, 1]gives6.[5, 3]gives3.[0, 0, 0, 1]gives0.