Facebook Pixel

441. Arranging Coins

Problem Description

You are given n coins and need to build a staircase with them. The staircase follows a specific pattern where:

  • Row 1 contains exactly 1 coin
  • Row 2 contains exactly 2 coins
  • Row 3 contains exactly 3 coins
  • And so on...

Each row i must contain exactly i coins. You can only count a row as complete if it has the exact required number of coins. The last row might not have enough coins to be complete, in which case it doesn't count toward your answer.

Your task is to determine how many complete rows you can build with the given n coins.

For example:

  • If n = 5, you can build 2 complete rows (row 1 uses 1 coin, row 2 uses 2 coins, leaving 2 coins which isn't enough to complete row 3 that needs 3 coins)
  • If n = 8, you can build 3 complete rows (row 1 uses 1 coin, row 2 uses 2 coins, row 3 uses 3 coins, totaling 6 coins, leaving 2 coins which isn't enough for row 4)

The solution uses a mathematical formula derived from the fact that the sum of the first k natural numbers is k * (k + 1) / 2. By solving the inequality k * (k + 1) / 2 ≤ n for k, we get the formula k = int(sqrt(2) * sqrt(n + 0.125) - 0.5), which directly calculates the number of complete rows.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that this is a problem about finding the largest triangular number that doesn't exceed n. When we build the staircase, we're essentially using 1 + 2 + 3 + ... + k coins for k complete rows.

The sum of the first k natural numbers follows the formula k * (k + 1) / 2. So we need to find the largest k where k * (k + 1) / 2 ≤ n.

To solve this, we can rearrange the inequality:

  • k * (k + 1) / 2 ≤ n
  • k² + k ≤ 2n
  • k² + k - 2n ≤ 0

This is a quadratic inequality. Using the quadratic formula where a = 1, b = 1, and c = -2n, we get:

  • k = (-1 ± sqrt(1 + 8n)) / 2

Since k must be positive (we can't have negative rows), we take the positive root:

  • k = (-1 + sqrt(1 + 8n)) / 2

This can be rewritten as:

  • k = sqrt(2n + 0.25) - 0.5
  • k = sqrt(2) * sqrt(n + 0.125) - 0.5

Since we need the number of complete rows, we take the floor of this value using int(). This mathematical formula directly gives us the answer in constant time without needing to iterate or use binary search.

The beauty of this approach is that it transforms what seems like a counting problem into a mathematical equation that can be solved instantly, regardless of how large n is.

Solution Implementation

1class Solution:
2    def arrangeCoins(self, n: int) -> int:
3        left, right = 1, n
4        first_true_index = 0  # 0 rows if n = 0
5
6        while left <= right:
7            mid = (left + right) // 2
8            # Check if we can form mid complete rows
9            coins_needed = mid * (mid + 1) // 2
10            if coins_needed <= n:
11                first_true_index = mid
12                left = mid + 1  # Try larger k
13            else:
14                right = mid - 1  # k is too large
15
16        return first_true_index
17
1class Solution {
2    public int arrangeCoins(int n) {
3        long left = 1, right = n;
4        long firstTrueIndex = 0;
5
6        while (left <= right) {
7            long mid = left + (right - left) / 2;
8            long coinsNeeded = mid * (mid + 1) / 2;
9            if (coinsNeeded <= n) {
10                firstTrueIndex = mid;
11                left = mid + 1;
12            } else {
13                right = mid - 1;
14            }
15        }
16
17        return (int) firstTrueIndex;
18    }
19}
20
1class Solution {
2public:
3    int arrangeCoins(int n) {
4        long long left = 1, right = n;
5        long long firstTrueIndex = 0;
6
7        while (left <= right) {
8            long long mid = left + (right - left) / 2;
9            long long coinsNeeded = mid * (mid + 1) / 2;
10            if (coinsNeeded <= n) {
11                firstTrueIndex = mid;
12                left = mid + 1;
13            } else {
14                right = mid - 1;
15            }
16        }
17
18        return firstTrueIndex;
19    }
20};
21
1function arrangeCoins(n: number): number {
2    let left: number = 1;
3    let right: number = n;
4    let firstTrueIndex: number = 0;
5
6    while (left <= right) {
7        const mid: number = Math.floor((left + right) / 2);
8        const coinsNeeded: number = Math.floor((mid * (mid + 1)) / 2);
9        if (coinsNeeded <= n) {
10            firstTrueIndex = mid;
11            left = mid + 1;
12        } else {
13            right = mid - 1;
14        }
15    }
16
17    return firstTrueIndex;
18}
19

Solution Approach

We use binary search to find the largest number of complete rows k where k * (k + 1) / 2 ≤ n.

Understanding the feasible function: For a given number of rows k, we need k * (k + 1) / 2 coins to complete all rows. The feasible condition is:

  • feasible(k) = true if k * (k + 1) / 2 ≤ n

This creates a boolean pattern:

k:         1     2     3     4     5     ...
coins:     1     3     6    10    15     ...
feasible:  true  true  true  true  false  ... (when n = 11)

We want to find the last true position, which is equivalent to finding the first k where k * (k + 1) / 2 > n.

Binary Search Template (finding first false, then subtract 1):

left, right = 1, n
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if mid * (mid + 1) // 2 <= n:  # feasible
        first_true_index = mid
        left = mid + 1  # Look for larger k
    else:
        right = mid - 1  # k is too large

return first_true_index

Why this works:

  • If k rows can be formed with n coins, we record it and try larger values
  • If k rows need more coins than n, we try smaller values
  • The answer is the largest k where the condition is true

Time Complexity: O(log n) - Binary search over the range [1, n] Space Complexity: O(1) - Only constant extra space used

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with n = 11 coins using binary search.

Step 1: Set up binary search

  • left = 1, right = 11
  • first_true_index = -1

Step 2: Binary search iterations

Iteration 1: left=1, right=11

  • mid = (1 + 11) // 2 = 6
  • Check: 6 * 7 / 2 = 21 > 11 → not feasible
  • right = 5

Iteration 2: left=1, right=5

  • mid = (1 + 5) // 2 = 3
  • Check: 3 * 4 / 2 = 6 ≤ 11 → feasible
  • first_true_index = 3, left = 4

Iteration 3: left=4, right=5

  • mid = (4 + 5) // 2 = 4
  • Check: 4 * 5 / 2 = 10 ≤ 11 → feasible
  • first_true_index = 4, left = 5

Iteration 4: left=5, right=5

  • mid = (5 + 5) // 2 = 5
  • Check: 5 * 6 / 2 = 15 > 11 → not feasible
  • right = 4

Loop ends: left = 5 > right = 4

Answer: first_true_index = 4

Verification: With 4 complete rows, we use:

  • Row 1: 1 coin
  • Row 2: 2 coins
  • Row 3: 3 coins
  • Row 4: 4 coins
  • Total: 1 + 2 + 3 + 4 = 10 coins

We have 11 coins, so 4 rows use 10, leaving 1 coin. Row 5 would need 5 coins, so we can't complete it.

Time and Space Complexity

Time Complexity: O(log n)

The solution uses binary search over the range [1, n]. In each iteration:

  • We calculate the mid value and check if mid * (mid + 1) / 2 ≤ n
  • Based on the result, we halve the search space

The number of iterations is proportional to log n, and each iteration performs constant-time arithmetic operations.

Space Complexity: O(1)

The solution only uses a constant amount of extra space for variables (left, right, mid, firstTrueIndex, coinsNeeded). No additional data structures or recursive calls are used.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

A common mistake is using while left < right with left = mid for finding the last true position, which can cause infinite loops.

Incorrect:

while left < right:
    mid = (left + right + 1) // 2  # Need +1 to avoid infinite loop
    if mid * (mid + 1) // 2 <= n:
        left = mid
    else:
        right = mid - 1
return left

Correct (template-compliant):

first_true_index = 0
while left <= right:
    mid = (left + right) // 2
    if mid * (mid + 1) // 2 <= n:
        first_true_index = mid
        left = mid + 1
    else:
        right = mid - 1
return first_true_index

The template approach avoids the need for +1 adjustments and infinite loop concerns.

2. Integer Overflow in Sum Calculation

When calculating mid * (mid + 1) / 2 for large values of mid, integer overflow can occur.

Incorrect (in Java/C++):

int mid = (left + right) / 2;
int sum = mid * (mid + 1) / 2;  // Overflow when mid > ~46340

Correct:

long mid = left + (right - left) / 2;
long sum = mid * (mid + 1) / 2;

Use long types for intermediate calculations when n can be large.

3. Off-by-One in Search Space

Setting incorrect initial bounds.

Incorrect:

left, right = 0, n  # left should start at 1 (at least 1 row possible for n >= 1)

Correct:

left, right = 1, n
first_true_index = 0  # Handle n = 0 edge case

4. Forgetting Edge Case n = 0

When n = 0, no rows can be formed. Initialize first_true_index = 0 to handle this case correctly.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More