title: Min Cost Climbing Stairs
slug: min-cost-climbing-stairs
difficulty: easy
leetcode_id: 746
leetcode_url: https://leetcode.com/problems/min-cost-climbing-stairs/
categories:
  - arrays
  - dynamic-programming
patterns:
  - dynamic-programming

description: |
  You are given an integer array `cost` where `cost[i]` is the cost of the i<sup>th</sup> 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*.

constraints: |
  - `2 <= cost.length <= 1000`
  - `0 <= cost[i] <= 999`

examples:
  - input: "cost = [10, 15, 20]"
    output: "15"
    explanation: "Start at index 1. Pay 15 and climb two steps to reach the top. Total cost is 15."
  - input: "cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]"
    output: "6"
    explanation: "Start at index 0. Pay costs at indices 0, 2, 4, 6, 7, 9 (all value 1), climbing strategically to avoid the 100-cost steps. Total cost is 6."

explanation:
  intuition: |
    Imagine you're climbing a staircase where each step has a toll booth. You must pay the toll to leave that step, and then you can jump either one or two steps forward. The goal is to reach the top (past the last step) while paying as little as possible.

    Think of it like this: standing at any step, you ask yourself "What's the **cheapest way** to have arrived here?" You could have come from one step back (paying that step's cost) or from two steps back (paying that step's cost). The minimum of these two options gives you the cheapest path to your current position.

    This is the **principle of optimality** — the optimal solution to the whole problem contains optimal solutions to subproblems. If you know the minimum cost to reach steps `i-1` and `i-2`, you can easily compute the minimum cost to reach step `i`.

    The key insight is that you don't need to track the actual path taken — only the **cumulative minimum cost** to reach each position. This transforms a potentially exponential problem (trying all paths) into a linear one.

  approach: |
    We solve this using **Space-Optimised Dynamic Programming**:

    **Step 1: Understand the goal**

    - The "top" is one position past the last step (index `n`)
    - You can start at index `0` or `1` without paying anything initially
    - You must pay `cost[i]` to leave step `i` and climb further

    &nbsp;

    **Step 2: Define the recurrence**

    - Let `dp[i]` = minimum cost to reach step `i`
    - `dp[0] = 0`: Starting at step 0 costs nothing (you haven't climbed yet)
    - `dp[1] = 0`: Starting at step 1 costs nothing (alternative starting point)
    - For `i >= 2`: `dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])`

    &nbsp;

    **Step 3: Optimise space**

    - Since we only need the last two values, use two variables instead of an array
    - `prev1`: Minimum cost to reach position `i-1`
    - `prev2`: Minimum cost to reach position `i-2`

    &nbsp;

    **Step 4: Iterate to the top**

    - Loop from position `2` to `n` (the top, past the last step)
    - At each position, calculate the minimum cost to arrive there
    - Update the sliding window: shift `prev2 = prev1`, `prev1 = current`

    &nbsp;

    **Step 5: Return the result**

    - After the loop, `prev1` contains the minimum cost to reach the top

  common_pitfalls:
    - title: Confusing "Reach Step" vs "Leave Step"
      description: |
        A common mistake is confusing when you pay the cost. You pay `cost[i]` when you **leave** step `i`, not when you arrive.

        This means:
        - Arriving at step `i` doesn't require paying `cost[i]` yet
        - The minimum cost to reach step `i` is `min(cost_to_reach[i-1] + cost[i-1], cost_to_reach[i-2] + cost[i-2])`
        - The "top" is at index `n`, one past the last element

        Getting this wrong leads to off-by-one errors and incorrect totals.
      wrong_approach: "dp[i] = min(dp[i-1], dp[i-2]) + cost[i]"
      correct_approach: "dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])"

    - title: Forgetting You Can Start at Index 0 or 1
      description: |
        The problem states you can start at either index `0` or `1`. This means:
        - `dp[0] = 0` (no cost to be at starting position 0)
        - `dp[1] = 0` (no cost to be at starting position 1)

        If you incorrectly set `dp[0] = cost[0]` or `dp[1] = cost[1]`, you're charging for arriving at a starting position, which isn't required.
      wrong_approach: "dp[0] = cost[0], dp[1] = cost[1]"
      correct_approach: "dp[0] = 0, dp[1] = 0 (starting positions are free)"

    - title: Using O(n) Space Unnecessarily
      description: |
        While an array `dp[0..n]` works correctly, you only ever need the last two computed values. Storing the entire array wastes space.

        With `n` up to 1000, this isn't critical for correctness, but the space-optimised O(1) solution is cleaner and demonstrates good DP technique.
      wrong_approach: "Maintaining full dp array of size n+1"
      correct_approach: "Use two variables to track only the last two values"

  key_takeaways:
    - "**State definition matters**: Clearly define what your DP state represents — 'cost to reach' vs 'cost to leave' changes the recurrence"
    - "**Space optimisation**: When the recurrence only depends on fixed previous states (here, the last 2), use variables instead of an array"
    - "**Extension of Climbing Stairs**: This problem adds a cost dimension to the classic stair-climbing problem — recognise this pattern"
    - "**Bottom-up efficiency**: Iterative DP avoids recursion overhead and makes the space optimisation natural"

  time_complexity: "O(n). We iterate once from position 2 to n, performing O(1) work at each step."
  space_complexity: "O(1). We only store two variables (`prev1` and `prev2`), regardless of input size."

solutions:
  - approach_name: Space-Optimised DP
    is_optimal: true
    code: |
      def min_cost_climbing_stairs(cost: list[int]) -> int:
          n = len(cost)

          # Minimum cost to reach step 0 or 1 (starting positions)
          prev2 = 0  # cost to reach position i-2
          prev1 = 0  # cost to reach position i-1

          # Calculate minimum cost to reach each position up to the top
          for i in range(2, n + 1):
              # To reach position i, either:
              # - Come from i-1, paying cost[i-1] to leave that step
              # - Come from i-2, paying cost[i-2] to leave that step
              current = min(prev1 + cost[i - 1], prev2 + cost[i - 2])

              # Slide the window forward
              prev2 = prev1
              prev1 = current

          return prev1
    explanation: |
      **Time Complexity:** O(n) — Single pass through the array.

      **Space Complexity:** O(1) — Only two variables needed.

      We iterate from position 2 to n (the top), computing the minimum cost to reach each position based on the two previous positions. The final value gives us the minimum cost to reach the top.

  - approach_name: DP with Array
    is_optimal: false
    code: |
      def min_cost_climbing_stairs(cost: list[int]) -> int:
          n = len(cost)

          # dp[i] = minimum cost to reach position i
          dp = [0] * (n + 1)

          # Starting positions are free
          dp[0] = 0
          dp[1] = 0

          # Fill in minimum cost for each position
          for i in range(2, n + 1):
              # Choose the cheaper path to reach position i
              dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])

          # Return cost to reach the top (position n)
          return dp[n]
    explanation: |
      **Time Complexity:** O(n) — Single pass to fill the DP array.

      **Space Complexity:** O(n) — Array of size n+1 to store intermediate costs.

      This approach explicitly stores all intermediate results in an array. While correct, it uses more space than necessary since we only need the last two values at any point. Useful for understanding the DP structure before applying space optimisation.
