title: Arranging Coins
slug: arranging-coins
difficulty: easy
leetcode_id: 441
leetcode_url: https://leetcode.com/problems/arranging-coins/
categories:
  - math
  - binary-search
patterns:
  - binary-search

function_signature: "def arrange_coins(n: int) -> int:"

test_cases:
  visible:
    - input: { n: 5 }
      expected: 2
    - input: { n: 8 }
      expected: 3
  hidden:
    - input: { n: 1 }
      expected: 1
    - input: { n: 3 }
      expected: 2
    - input: { n: 6 }
      expected: 3
    - input: { n: 10 }
      expected: 4
    - input: { n: 100 }
      expected: 13

description: |
  You have `n` coins and you want to build a staircase with these coins. The staircase consists of `k` rows where the i<sup>th</sup> row has exactly `i` coins. The last row of the staircase **may be** incomplete.

  Given the integer `n`, return *the number of **complete rows** of the staircase you will build*.

constraints: |
  - `1 <= n <= 2^31 - 1`

examples:
  - input: "n = 5"
    output: "2"
    explanation: "Because the 3rd row is incomplete, we return 2."
  - input: "n = 8"
    output: "3"
    explanation: "Because the 4th row is incomplete, we return 3."

explanation:
  intuition: |
    Imagine stacking coins row by row: the 1<sup>st</sup> row needs 1 coin, the 2<sup>nd</sup> row needs 2 coins, the 3<sup>rd</sup> row needs 3 coins, and so on.

    The total number of coins needed for `k` complete rows is `1 + 2 + 3 + ... + k`, which equals `k(k+1)/2` (the triangular number formula).

    Think of it like this: we're searching for the **largest value of k** such that the sum of the first k natural numbers does not exceed n. This is essentially asking: "What's the biggest staircase I can fully build with n coins?"

    Since the sum `k(k+1)/2` increases monotonically with k, we can use **binary search** to efficiently find the answer. Alternatively, we can solve the quadratic equation directly using the quadratic formula for an O(1) mathematical solution.

  approach: |
    We can solve this using **Binary Search** on the number of rows:

    **Step 1: Define the search space**

    - `left`: Set to `1` (minimum possible complete rows)
    - `right`: Set to `n` (maximum possible rows, though we'll never need this many)

    &nbsp;

    **Step 2: Binary search for the largest valid k**

    - Calculate `mid` as the midpoint of the current range
    - Compute the coins needed for `mid` complete rows: `mid * (mid + 1) / 2`
    - If coins needed equals `n`, we found an exact match - return `mid`
    - If coins needed is less than `n`, we might be able to fit more rows - search right
    - If coins needed is greater than `n`, we need fewer rows - search left

    &nbsp;

    **Step 3: Return the result**

    - When the loop ends, `right` contains the largest k where `k(k+1)/2 <= n`
    - Return `right` as the number of complete rows

    &nbsp;

    The binary search efficiently narrows down the answer in O(log n) time instead of simulating the stacking process.

  common_pitfalls:
    - title: The Simulation Trap
      description: |
        A naive approach is to simulate building the staircase row by row:
        - Start with row 1, subtract 1 coin
        - Move to row 2, subtract 2 coins
        - Continue until you can't complete a row

        While correct, this takes **O(sqrt(n)) time** because you'll build approximately sqrt(2n) rows. For `n = 2^31 - 1`, that's about 65,000 iterations. Binary search does it in about 31 iterations.
      wrong_approach: "Simulate stacking row by row"
      correct_approach: "Binary search on the number of rows"

    - title: Integer Overflow
      description: |
        When computing `mid * (mid + 1) / 2`, the multiplication can overflow if `mid` is large. With `n` up to `2^31 - 1`, `mid` could be around `65,000`, and `mid * (mid + 1)` could exceed 32-bit integer limits.

        In Python this isn't an issue due to arbitrary precision integers, but in languages like Java or C++, you need to use `long` or divide before multiplying: `mid / 2 * (mid + 1)` or `mid * (mid + 1) / 2` with long types.
      wrong_approach: "Use 32-bit integers without overflow protection"
      correct_approach: "Use 64-bit integers or Python's arbitrary precision"

    - title: Off-by-One Errors
      description: |
        Binary search boundary conditions can be tricky. A common mistake is returning `left` instead of `right`, or using the wrong comparison operator.

        The key insight: we want the **largest** k where `k(k+1)/2 <= n`. When the search converges, `right` holds this value because we move `right = mid - 1` when we have too many coins.
      wrong_approach: "Incorrect boundary handling in binary search"
      correct_approach: "Carefully track which boundary holds the answer"

  key_takeaways:
    - "**Triangular number formula**: The sum `1 + 2 + ... + k = k(k+1)/2` appears frequently in problems involving sequential additions"
    - "**Binary search on answer**: When searching for the largest/smallest value satisfying a condition, binary search is often applicable"
    - "**Mathematical solutions**: Some binary search problems have closed-form solutions using algebra (quadratic formula here)"
    - "**Monotonic property**: Binary search works because `k(k+1)/2` strictly increases with k - if k rows need more than n coins, so will k+1 rows"

  time_complexity: "O(log n). Binary search halves the search space each iteration, and the search space is at most n."
  space_complexity: "O(1). We only use a constant number of variables (`left`, `right`, `mid`, `coins_needed`)."

solutions:
  - approach_name: Binary Search
    is_optimal: true
    code: |
      def arrange_coins(n: int) -> int:
          left, right = 1, n

          while left <= right:
              mid = left + (right - left) // 2
              # Coins needed for mid complete rows
              coins_needed = mid * (mid + 1) // 2

              if coins_needed == n:
                  # Exact fit - mid rows use exactly n coins
                  return mid
              elif coins_needed < n:
                  # We have coins left over, try more rows
                  left = mid + 1
              else:
                  # Not enough coins, try fewer rows
                  right = mid - 1

          # right is the largest k where k(k+1)/2 <= n
          return right
    explanation: |
      **Time Complexity:** O(log n) - Binary search on the range [1, n].

      **Space Complexity:** O(1) - Only constant extra space used.

      We binary search for the largest k such that k(k+1)/2 <= n. Each iteration halves the search space, and we use the triangular number formula to compute coins needed in O(1).

  - approach_name: Mathematical (Quadratic Formula)
    is_optimal: true
    code: |
      import math

      def arrange_coins(n: int) -> int:
          # We need the largest k where k(k+1)/2 <= n
          # Solving k^2 + k - 2n = 0 using quadratic formula:
          # k = (-1 + sqrt(1 + 8n)) / 2
          return int((-1 + math.sqrt(1 + 8 * n)) / 2)
    explanation: |
      **Time Complexity:** O(1) - Direct calculation using the quadratic formula.

      **Space Complexity:** O(1) - Only constant extra space used.

      We solve k(k+1)/2 <= n algebraically. Rearranging to k^2 + k - 2n <= 0 and applying the quadratic formula gives k = (-1 + sqrt(1 + 8n)) / 2. Taking the floor gives the largest valid k.

  - approach_name: Linear Simulation
    is_optimal: false
    code: |
      def arrange_coins(n: int) -> int:
          rows = 0
          coins_remaining = n

          # Build rows one by one until we can't complete one
          while coins_remaining >= rows + 1:
              rows += 1
              coins_remaining -= rows

          return rows
    explanation: |
      **Time Complexity:** O(sqrt(n)) - We build approximately sqrt(2n) rows.

      **Space Complexity:** O(1) - Only constant extra space used.

      This simulates the stacking process directly. While intuitive and correct, it's slower than the binary search or mathematical approaches. Included to show the progression from brute force to optimal solution.
