Min Cost Climbing Stairs – Solution & Complexity

Solution Walkthrough

  1. Step 1: Frame the recurrence

    • Let best[i] be the cheapest toll paid to arrive at stair i.
    • Arriving at the top means arriving at index n, one past the last stair.
    • You reach stair i from either stair i-1 or stair i-2, paying that stair's toll when you leave it.
  2. Step 2: Tabulate over a DP array

    • best[0] = best[1] = 0 because you may start on either stair for free.
    • For every later index, take the cheaper of the two incoming moves and add the toll you paid to make that move.
    • The answer is best[n].
    def min_cost_climbing_stairs(cost: list[int]) -> int:
        n = len(cost)
        dp = [0] * (n + 1)
        for i in range(2, n + 1):
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
        return dp[n]
  3. Step 3: Collapse to two rolling variables

    • The recurrence only ever looks back two positions, so a full array is unnecessary.
    • Track downOne (cost to reach the previous stair) and downTwo (the stair before that) and slide them forward.
  4. Step 4: Optimal O(1)-space scan

    • Iterate from index 2 through n, computing each new arrival cost from the two rolling variables, then shift them.
    • After the loop, downOne holds the cost to reach the top.
    def min_cost_climbing_stairs(cost: list[int]) -> int:
        down_two, down_one = 0, 0
        for i in range(2, len(cost) + 1):
            step = min(down_one + cost[i - 1], down_two + cost[i - 2])
            down_two, down_one = down_one, step
        return down_one
  5. Step 5: Dry run

    Trace cost = [10,15,20].

    idownTwodownOnenew step
    200min(0+15, 0+10) = 10
    3010min(10+20, 0+15) = 15

    The loop ends with downOne = 15, matching the expected answer.

  6. Step 6: Common mistakes and follow-ups

    • Returning dp[n-1] instead of dp[n], which stops one stair short of the top.
    • Adding cost[i] when arriving instead of cost[i-1]/cost[i-2] for the stair you leave.
    • Forgetting that both stair 0 and stair 1 are free starting points.
    • Follow-up: how would the recurrence change if you could also climb three stairs at a time?
  7. Step 7: Edge cases to test mentally

    • The minimum length is 2; the answer is then min(cost[0], cost[1]).
    • All-zero tolls cost 0.
    • Alternating free and expensive stairs should always land on the free ones.
  8. Step 8: Final full solution and complexity

    A single left-to-right scan keeps two rolling subresults. Time is O(n) and extra space is O(1).

    def min_cost_climbing_stairs(cost: list[int]) -> int:
        down_two, down_one = 0, 0
        for i in range(2, len(cost) + 1):
            step = min(down_one + cost[i - 1], down_two + cost[i - 2])
            down_two, down_one = down_one, step
        return down_one

FAQ