Facebook Pixel

483. Smallest Good Base

Problem Description

Given an integer n as a string, find the smallest base k (where k ≥ 2) such that when n is represented in base k, all its digits are 1's.

In other words, we need to find the smallest integer k where:

  • n = 1 + k + k² + k³ + ... + k^m for some integer m ≥ 0
  • This means n in base k would be written as "111...1" (all 1's)

For example:

  • If n = 13, the smallest good base is 3 because 13 in base 3 equals 111 (since 1×3² + 1×3¹ + 1×3⁰ = 9 + 3 + 1 = 13)
  • If n = 4681, the smallest good base is 8 because 4681 = 1 + 8 + 8² + 8³ + 8⁴ = 1 + 8 + 64 + 512 + 4096 = 4681

The solution uses binary search to find the appropriate base. For each possible number of digits m (from largest to smallest), it searches for a base k such that the sum 1 + k + k² + ... + k^m equals n. The function cal(k, m) computes this geometric series sum. By iterating through different values of m and using binary search to find the corresponding k, the algorithm finds the smallest valid base.

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

Intuition

The key insight is recognizing that we're looking for a base k where n = 1 + k + k² + ... + k^m. This is a geometric series with sum formula: n = (k^(m+1) - 1) / (k - 1).

Since we want the smallest base, we should start by checking if n can be represented with more digits (larger m), as having more 1's typically requires a smaller base. For instance, 111 in base 2 is smaller than 11 in base 6, even though both equal 7.

The maximum number of digits possible is around 63 because even in base 2 (the smallest valid base), we can't exceed the range of a 64-bit integer. So we iterate m from 63 down to 2.

For each fixed number of digits m, we need to find if there exists a base k such that 1 + k + k² + ... + k^m = n. As k increases, this sum grows monotonically, making binary search perfect for finding the exact k value.

The search range for k is from 2 (minimum valid base) to n-1 (maximum meaningful base, since in base n or higher, n would be represented as a single digit).

Why return str(num - 1) at the end? If no valid base is found for m ≥ 2, then we need m = 1, meaning n = 1 + k. This gives us k = n - 1, which always works as n in base n-1 equals 11 (one (n-1) plus one unit).

This approach guarantees finding the smallest good base by systematically checking from the maximum possible number of digits down to the minimum.

Solution Implementation

1class Solution:
2    def smallestGoodBase(self, n: str) -> str:
3        def calculate_sum(base: int, num_terms: int) -> int:
4            """
5            Calculate the sum: 1 + base + base^2 + ... + base^num_terms
6            """
7            power = 1
8            total = 1
9            for _ in range(num_terms):
10                power *= base
11                total += power
12            return total
13
14        def feasible(base: int, num_terms: int, target: int) -> bool:
15            """Check if sum of powers >= target (feasible condition)"""
16            return calculate_sum(base, num_terms) >= target
17
18        target = int(n)
19
20        # Try different digit counts from largest to smallest
21        for num_terms in range(63, 1, -1):
22            # Binary search template: find first base where sum >= target
23            left, right = 2, target - 1
24            first_true_index = -1
25
26            while left <= right:
27                mid = (left + right) // 2
28                if feasible(mid, num_terms, target):
29                    first_true_index = mid
30                    right = mid - 1  # Search for smaller base
31                else:
32                    left = mid + 1
33
34            # Check if we found an exact match
35            if first_true_index != -1 and calculate_sum(first_true_index, num_terms) == target:
36                return str(first_true_index)
37
38        # Fallback: m = 1 case, n = 1 + (n-1)
39        return str(target - 1)
40
1class Solution {
2    public String smallestGoodBase(String n) {
3        long target = Long.parseLong(n);
4
5        // Try different digit counts from largest to smallest
6        for (int numTerms = 63; numTerms >= 2; numTerms--) {
7            long base = findBase(numTerms, target);
8            if (base != -1) {
9                return String.valueOf(base);
10            }
11        }
12
13        // Fallback: m = 1 case, n = 1 + (n-1)
14        return String.valueOf(target - 1);
15    }
16
17    /**
18     * Binary search template: find the smallest base where sum >= target,
19     * then check if it's an exact match.
20     */
21    private long findBase(int numTerms, long target) {
22        long left = 2;
23        long right = target - 1;
24        long firstTrueIndex = -1;
25
26        while (left <= right) {
27            long mid = left + (right - left) / 2;
28            if (feasible(mid, numTerms, target)) {
29                firstTrueIndex = mid;
30                right = mid - 1;  // Search for smaller base
31            } else {
32                left = mid + 1;
33            }
34        }
35
36        // Check if we found an exact match
37        if (firstTrueIndex != -1 && calculateSum(firstTrueIndex, numTerms) == target) {
38            return firstTrueIndex;
39        }
40        return -1;
41    }
42
43    private boolean feasible(long base, int numTerms, long target) {
44        return calculateSum(base, numTerms) >= target;
45    }
46
47    /**
48     * Calculate: 1 + base + base^2 + ... + base^numTerms
49     * Returns Long.MAX_VALUE on overflow.
50     */
51    private long calculateSum(long base, int numTerms) {
52        long power = 1;
53        long sum = 1;
54
55        for (int i = 0; i < numTerms; i++) {
56            // Check for overflow
57            if (power > Long.MAX_VALUE / base) {
58                return Long.MAX_VALUE;
59            }
60            power *= base;
61            if (sum > Long.MAX_VALUE - power) {
62                return Long.MAX_VALUE;
63            }
64            sum += power;
65        }
66
67        return sum;
68    }
69}
70
1class Solution {
2public:
3    string smallestGoodBase(string n) {
4        long target = stol(n);
5
6        // Try different digit counts from largest to smallest
7        for (int numTerms = 63; numTerms >= 2; --numTerms) {
8            long base = findBase(numTerms, target);
9            if (base != -1) {
10                return to_string(base);
11            }
12        }
13
14        // Fallback: m = 1 case, n = 1 + (n-1)
15        return to_string(target - 1);
16    }
17
18private:
19    // Binary search template: find smallest base where sum >= target
20    long findBase(int numTerms, long target) {
21        long left = 2;
22        long right = target - 1;
23        long firstTrueIndex = -1;
24
25        while (left <= right) {
26            long mid = left + (right - left) / 2;
27            if (feasible(mid, numTerms, target)) {
28                firstTrueIndex = mid;
29                right = mid - 1;  // Search for smaller base
30            } else {
31                left = mid + 1;
32            }
33        }
34
35        // Check if we found an exact match
36        if (firstTrueIndex != -1 && calculateSum(firstTrueIndex, numTerms) == target) {
37            return firstTrueIndex;
38        }
39        return -1;
40    }
41
42    bool feasible(long base, int numTerms, long target) {
43        return calculateSum(base, numTerms) >= target;
44    }
45
46    // Calculate: 1 + base + base^2 + ... + base^numTerms
47    long calculateSum(long base, int numTerms) {
48        long power = 1;
49        long sum = 1;
50
51        for (int i = 0; i < numTerms; ++i) {
52            // Check for overflow
53            if (power > LONG_MAX / base) {
54                return LONG_MAX;
55            }
56            power *= base;
57            if (sum > LONG_MAX - power) {
58                return LONG_MAX;
59            }
60            sum += power;
61        }
62
63        return sum;
64    }
65};
66
1function smallestGoodBase(n: string): string {
2    const target = parseInt(n);
3
4    // Helper: calculate 1 + base + base^2 + ... + base^numTerms
5    function calculateSum(base: number, numTerms: number): number {
6        let power = 1;
7        let sum = 1;
8        for (let i = 0; i < numTerms; i++) {
9            power *= base;
10            sum += power;
11        }
12        return sum;
13    }
14
15    // Feasible condition: sum >= target
16    function feasible(base: number, numTerms: number): boolean {
17        return calculateSum(base, numTerms) >= target;
18    }
19
20    // Binary search template: find smallest base where sum >= target
21    function findBase(numTerms: number): number {
22        let left = 2;
23        let right = target - 1;
24        let firstTrueIndex = -1;
25
26        while (left <= right) {
27            const mid = Math.floor((left + right) / 2);
28            if (feasible(mid, numTerms)) {
29                firstTrueIndex = mid;
30                right = mid - 1;  // Search for smaller base
31            } else {
32                left = mid + 1;
33            }
34        }
35
36        // Check if we found an exact match
37        if (firstTrueIndex !== -1 && calculateSum(firstTrueIndex, numTerms) === target) {
38            return firstTrueIndex;
39        }
40        return -1;
41    }
42
43    // Try different digit counts from largest to smallest
44    for (let numTerms = 63; numTerms >= 2; numTerms--) {
45        const base = findBase(numTerms);
46        if (base !== -1) {
47            return base.toString();
48        }
49    }
50
51    // Fallback: m = 1 case, n = 1 + (n-1)
52    return (target - 1).toString();
53}
54

Solution Approach

The implementation uses binary search within a loop to systematically find the smallest good base, following the standard binary search template.

Helper Function calculate_sum(base, num_terms): This function calculates the geometric series sum 1 + base + base² + ... + base^num_terms. It initializes power = 1 (current power of base) and total = 1 (sum starting with the first term). Then it iterates num_terms times, multiplying power by base to get the next power and adding it to the sum.

Main Algorithm:

  1. Convert the string n to an integer target for numerical operations.

  2. Iterate through possible digit counts m from 63 down to 2:

    • We start from 63 because that's approximately the maximum number of bits needed to represent large integers
    • We check larger m values first because more digits generally means a smaller base
  3. For each m, use binary search with the standard template to find the base k:

    • Initialize: left = 2, right = target - 1, first_true_index = -1
    • The feasible condition is calculate_sum(mid, m) >= target
    • Binary search loop: while left <= right
      • Calculate midpoint: mid = (left + right) // 2
      • If feasible(mid): store first_true_index = mid, then search for smaller bases (right = mid - 1)
      • Otherwise: search larger bases (left = mid + 1)
  4. After binary search, check if first_true_index != -1 and calculate_sum(first_true_index, m) == target:

    • If yes, we found an exact match! Return str(first_true_index) as the smallest good base for this m
    • If no exact match, continue to the next smaller m
  5. If no base is found for any m >= 2, return str(target - 1):

    • This handles the case where m = 1, meaning n = 1 + k
    • Therefore k = n - 1, and n in base (n-1) equals 11

The algorithm efficiently finds the smallest good base by leveraging the monotonic property of the geometric series and using binary search to quickly locate valid bases for each possible digit count.

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 trace through finding the smallest good base for n = "13" using the standard binary search template.

Step 1: Convert string to integer: target = 13

Step 2: Try different digit counts m from 63 down to 2.

For m = 63, 62, ..., 4:

  • When we calculate calculate_sum(2, m) for these values, we get sums much larger than 13
  • Binary search finds no exact match for any of these m values
  • So we continue to smaller m values

Step 3: When m = 3 (looking for 4 digits):

  • Initialize: left = 2, right = 12, first_true_index = -1

  • Feasible condition: calculate_sum(mid, 3) >= 13

  • First iteration: mid = 7

    • calculate_sum(7, 3) = 1 + 7 + 49 + 343 = 400 >= 13 ✓ (feasible)
    • Update: first_true_index = 7, right = 6
  • Second iteration: mid = 4

    • calculate_sum(4, 3) = 1 + 4 + 16 + 64 = 85 >= 13 ✓ (feasible)
    • Update: first_true_index = 4, right = 3
  • Third iteration: mid = 2

    • calculate_sum(2, 3) = 1 + 2 + 4 + 8 = 15 >= 13 ✓ (feasible)
    • Update: first_true_index = 2, right = 1
  • Loop ends: left > right

  • Check: calculate_sum(2, 3) = 15 ≠ 13, so no exact match for m = 3

Step 4: When m = 2 (looking for 3 digits):

  • Initialize: left = 2, right = 12, first_true_index = -1

  • Feasible condition: calculate_sum(mid, 2) >= 13

  • First iteration: mid = 7

    • calculate_sum(7, 2) = 1 + 7 + 49 = 57 >= 13 ✓ (feasible)
    • Update: first_true_index = 7, right = 6
  • Second iteration: mid = 4

    • calculate_sum(4, 2) = 1 + 4 + 16 = 21 >= 13 ✓ (feasible)
    • Update: first_true_index = 4, right = 3
  • Third iteration: mid = 2

    • calculate_sum(2, 2) = 1 + 2 + 4 = 7 < 13 ✗ (not feasible)
    • Update: left = 3
  • Fourth iteration: mid = 3

    • calculate_sum(3, 2) = 1 + 3 + 9 = 13 >= 13 ✓ (feasible)
    • Update: first_true_index = 3, right = 2
  • Loop ends: left > right

  • Check: calculate_sum(3, 2) = 13 == 13 ✓ Exact match!

Step 5: Return "3"

Indeed, 13 in base 3 equals 111 because 1×3² + 1×3¹ + 1×3⁰ = 9 + 3 + 1 = 13.

Time and Space Complexity

Time Complexity: O(log²(n))

The algorithm iterates through possible lengths m from 63 down to 2, which takes O(log(n)) iterations since the maximum useful value of m is approximately log₂(n). For each value of m, a binary search is performed on the range [2, n-1], which takes O(log(n)) iterations. Within each binary search iteration, the cal function is called, which performs m multiplications and additions, taking O(m) = O(log(n)) time. Therefore, the total time complexity is O(log(n) × log(n) × log(n)) = O(log³(n)).

However, upon closer analysis, the outer loop runs for at most 63 iterations (a constant), so we can consider it as O(1) in terms of n. The binary search takes O(log(n)) iterations, and each call to cal takes O(log(n)) time. This gives us a more precise complexity of O(log²(n)).

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space for variables num, m, l, r, mid, and the variables within the cal function (p, s, i). No additional data structures that scale with the input size are used. The input is a string representation of a number, but we only store its integer conversion and a few working variables.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

A common mistake is using while left < right with right = mid instead of the standard template with while left <= right and right = mid - 1.

Incorrect (non-standard template):

while left < right:
    mid = (left + right) // 2
    if calculate_sum(mid, num_terms) >= target:
        right = mid
    else:
        left = mid + 1
return left

Correct (standard template):

first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if feasible(mid, num_terms, target):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

The standard template with first_true_index clearly tracks whether a valid answer was found and handles edge cases consistently.

2. Integer Overflow in Power Calculation

When base and num_terms are large, computing base^num_terms can exceed the maximum integer value, causing incorrect results or runtime errors.

Solution: Add overflow checking to prevent invalid calculations:

def calculate_sum(base: int, num_terms: int) -> int:
    power = 1
    total = 1
    for _ in range(num_terms):
        if power > (10**18) // base:
            return float('inf')
        power *= base
        total += power
    return total

3. Not Checking for Exact Match

The binary search finds the first base where sum >= target, but we need an exact match where sum == target. Forgetting this check leads to incorrect results.

Solution: After binary search completes, always verify:

if first_true_index != -1 and calculate_sum(first_true_index, num_terms) == target:
    return str(first_true_index)

4. Edge Case: n = "3"

The code returns str(target - 1) when no base is found for m >= 2. This handles m = 1 case where n = 1 + k, giving k = n - 1. For n = "3", this correctly returns "2" since 3 in base 2 is "11".

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

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

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

Load More