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 Approach

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

Helper Function cal(k, m): This function calculates the geometric series sum 1 + k + k² + ... + k^m. It initializes p = 1 (current power of k) and s = 1 (sum starting with the first term). Then it iterates m times, multiplying p by k to get the next power and adding it to the sum. This computes the value that n would need to equal if represented as (m+1) ones in base k.

Main Algorithm:

  1. Convert the string n to an integer num 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 to find the base k:

    • Set search range: l = 2 (minimum valid base), r = num - 1 (maximum meaningful base)
    • Binary search loop: while l < r
      • Calculate midpoint: mid = (l + r) >> 1 (bit shift for integer division by 2)
      • If cal(mid, m) >= num: the sum is too large or exact, so search smaller bases (r = mid)
      • Otherwise: the sum is too small, search larger bases (l = mid + 1)
  4. After binary search converges to l, check if cal(l, m) == num:

    • If yes, we found an exact match! Return str(l) as the smallest good base for this m
    • If no, continue to the next smaller m
  5. If no base is found for any m >= 2, return str(num - 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".

Step 1: Convert string to integer: num = 13

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

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

  • When we calculate cal(2, m) for these values, we get sums much larger than 13
  • Binary search will converge to l = 2, but cal(2, m) > 13 for all these m values
  • So we continue to smaller m values

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

  • Binary search range: l = 2, r = 12
  • First iteration: mid = 7
    • cal(7, 3) = 1 + 7 + 49 + 343 = 400 (too large)
    • Update: r = 7
  • Second iteration: mid = 4
    • cal(4, 3) = 1 + 4 + 16 + 64 = 85 (too large)
    • Update: r = 4
  • Third iteration: mid = 3
    • cal(3, 3) = 1 + 3 + 9 + 27 = 40 (too large)
    • Update: r = 3
  • Fourth iteration: mid = 2
    • cal(2, 3) = 1 + 2 + 4 + 8 = 15 (too large)
    • Update: r = 2
  • Loop ends with l = 2
  • Check: cal(2, 3) = 15 ≠ 13, so no solution for m = 3

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

  • Binary search range: l = 2, r = 12
  • First iteration: mid = 7
    • cal(7, 2) = 1 + 7 + 49 = 57 (too large)
    • Update: r = 7
  • Second iteration: mid = 4
    • cal(4, 2) = 1 + 4 + 16 = 21 (too large)
    • Update: r = 4
  • Third iteration: mid = 3
    • cal(3, 2) = 1 + 3 + 9 = 13 (exactly equal!)
    • Update: r = 3
  • Loop ends with l = 3
  • Check: cal(3, 2) = 13 == 13

Step 5: Return "3"

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

Solution Implementation

1class Solution:
2    def smallestGoodBase(self, n: str) -> str:
3        def calculate_sum_of_powers(base: int, num_terms: int) -> int:
4            """
5            Calculate the sum: 1 + base + base^2 + ... + base^num_terms
6          
7            Args:
8                base: The base value k
9                num_terms: Number of additional terms after 1 (i.e., m in the formula)
10          
11            Returns:
12                The sum of the geometric series
13            """
14            power = 1  # Current power of base
15            total_sum = 1  # Initialize with the first term (base^0 = 1)
16          
17            # Add base^1, base^2, ..., base^num_terms
18            for i in range(num_terms):
19                power *= base
20                total_sum += power
21          
22            return total_sum
23      
24        # Convert string input to integer
25        target_number = int(n)
26      
27        # Try different values of m (number of terms - 1) from largest to smallest
28        # Maximum m is 63 because 2^64 > 10^18 (max value for n)
29        for num_additional_terms in range(63, 1, -1):
30            # Binary search for the base k that gives us exactly target_number
31            # For m terms, minimum base is 2, maximum is target_number - 1
32            left, right = 2, target_number - 1
33          
34            while left < right:
35                mid = (left + right) >> 1  # Equivalent to // 2
36              
37                # Check if current base gives a sum >= target
38                if calculate_sum_of_powers(mid, num_additional_terms) >= target_number:
39                    right = mid  # Try smaller bases
40                else:
41                    left = mid + 1  # Try larger bases
42          
43            # Check if we found an exact match
44            if calculate_sum_of_powers(left, num_additional_terms) == target_number:
45                return str(left)
46      
47        # If no valid base found for m >= 2, return target_number - 1
48        # This corresponds to m = 1 case: n = 1 + (n-1)
49        return str(target_number - 1)
50
1class Solution {
2    /**
3     * Finds the smallest good base for a given number n.
4     * A good base k means n can be represented as 1 + k + k^2 + ... + k^(m-1) for some m >= 2
5     * 
6     * @param n The input number as a string
7     * @return The smallest good base as a string
8     */
9    public String smallestGoodBase(String n) {
10        long num = Long.parseLong(n);
11      
12        // Try different lengths of representation from longest to shortest
13        // Maximum length is 63 because 2^63 > 10^18 (max value of n)
14        for (int length = 63; length >= 2; length--) {
15            long base = getRadix(length, num);
16            if (base != -1) {
17                return String.valueOf(base);
18            }
19        }
20      
21        // If no valid base found for length >= 2, return n-1
22        // This represents n = 1 + (n-1)^1
23        return String.valueOf(num - 1);
24    }
25
26    /**
27     * Finds the base (radix) that can represent num with given length using binary search.
28     * We're looking for base k such that 1 + k + k^2 + ... + k^(length-1) = num
29     * 
30     * @param length The number of terms in the geometric series
31     * @param num The target number to represent
32     * @return The base if found, -1 otherwise
33     */
34    private long getRadix(int length, long num) {
35        long left = 2;
36        long right = num - 1;
37      
38        // Binary search for the correct base
39        while (left < right) {
40            long mid = (left + right) >>> 1;  // Unsigned right shift to avoid overflow
41          
42            if (calc(mid, length) >= num) {
43                right = mid;
44            } else {
45                left = mid + 1;
46            }
47        }
48      
49        // Check if the found base gives exactly num
50        return calc(right, length) == num ? right : -1;
51    }
52
53    /**
54     * Calculates the sum of geometric series: 1 + radix + radix^2 + ... + radix^(length-1)
55     * Handles overflow by returning Long.MAX_VALUE when overflow occurs
56     * 
57     * @param radix The base of the geometric series
58     * @param length The number of terms in the series
59     * @return The sum of the geometric series or Long.MAX_VALUE if overflow
60     */
61    private long calc(long radix, int length) {
62        long power = 1;
63        long sum = 0;
64      
65        for (int i = 0; i < length; i++) {
66            // Check for sum overflow before adding
67            if (Long.MAX_VALUE - sum < power) {
68                return Long.MAX_VALUE;
69            }
70            sum += power;
71          
72            // Check for multiplication overflow before multiplying
73            if (Long.MAX_VALUE / power < radix) {
74                power = Long.MAX_VALUE;
75            } else {
76                power *= radix;
77            }
78        }
79      
80        return sum;
81    }
82}
83
1class Solution {
2public:
3    string smallestGoodBase(string n) {
4        // Convert string n to long integer
5        long targetValue = stol(n);
6      
7        // Calculate maximum possible number of digits in base-k representation
8        // For n = k^m + k^(m-1) + ... + k + 1, the maximum m occurs when k=2
9        int maxDigits = floor(log(targetValue) / log(2));
10      
11        // Try different digit lengths from maximum to 2
12        // We want the smallest base, so we try larger digit counts first
13        for (int digitCount = maxDigits; digitCount > 1; --digitCount) {
14            // Calculate potential base k using the formula:
15            // n = (k^(m+1) - 1) / (k - 1), which gives k ≈ n^(1/m)
16            int base = pow(targetValue, 1.0 / digitCount);
17          
18            // Calculate the sum: 1 + k + k^2 + ... + k^m
19            long powerOfBase = 1;  // Tracks k^i
20            long sum = 1;           // Running sum of geometric series
21          
22            for (int i = 0; i < digitCount; ++i) {
23                powerOfBase *= base;
24                sum += powerOfBase;
25            }
26          
27            // Check if this base gives us exactly the target value
28            if (sum == targetValue) {
29                return to_string(base);
30            }
31        }
32      
33        // If no base found for m > 1, then the answer is n-1
34        // This represents n in base (n-1) as "11" (two digits)
35        return to_string(targetValue - 1);
36    }
37};
38
1function smallestGoodBase(n: string): string {
2    // Convert string n to number
3    const targetValue: number = parseInt(n);
4  
5    // Calculate maximum possible number of digits in base-k representation
6    // For n = k^m + k^(m-1) + ... + k + 1, the maximum m occurs when k=2
7    const maxDigits: number = Math.floor(Math.log(targetValue) / Math.log(2));
8  
9    // Try different digit lengths from maximum to 2
10    // We want the smallest base, so we try larger digit counts first
11    for (let digitCount = maxDigits; digitCount > 1; digitCount--) {
12        // Calculate potential base k using the formula:
13        // n = (k^(m+1) - 1) / (k - 1), which gives k ≈ n^(1/m)
14        const base: number = Math.floor(Math.pow(targetValue, 1.0 / digitCount));
15      
16        // Calculate the sum: 1 + k + k^2 + ... + k^m
17        let powerOfBase: number = 1;  // Tracks k^i
18        let sum: number = 1;           // Running sum of geometric series
19      
20        for (let i = 0; i < digitCount; i++) {
21            powerOfBase *= base;
22            sum += powerOfBase;
23        }
24      
25        // Check if this base gives us exactly the target value
26        if (sum === targetValue) {
27            return base.toString();
28        }
29    }
30  
31    // If no base found for m > 1, then the answer is n-1
32    // This represents n in base (n-1) as "11" (two digits)
33    return (targetValue - 1).toString();
34}
35

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. Integer Overflow in Power Calculation

The most critical pitfall in this solution is potential integer overflow when calculating calculate_sum_of_powers(base, num_terms). When base and num_terms are large, computing base^num_terms can exceed the maximum integer value, causing incorrect results or runtime errors.

Example of the issue:

# This could overflow for large base and num_terms
power *= base  # If power is already large, this multiplication can overflow

Solution: Add overflow checking to prevent invalid calculations:

def calculate_sum_of_powers(base: int, num_terms: int) -> int:
    power = 1
    total_sum = 1
  
    for i in range(num_terms):
        # Check for potential overflow before multiplication
        if power > (10**18) // base:  # Assuming n <= 10^18
            return float('inf')  # Return infinity to indicate overflow
        power *= base
        total_sum += power
        if total_sum > 10**18:  # Early termination if sum exceeds possible n
            return float('inf')
  
    return total_sum

2. Incorrect Binary Search Boundary

Setting right = target_number - 1 as the upper bound might seem logical, but it's unnecessarily large for small values of m. This can lead to overflow issues and inefficient searches.

Better approach: Calculate a tighter upper bound based on the number of terms:

# For m additional terms, the maximum meaningful base can be calculated
# n = 1 + k + k^2 + ... + k^m < k^(m+1)/(k-1) < 2*k^m for k >= 2
# Therefore, k < n^(1/m)
import math

for num_additional_terms in range(63, 1, -1):
    left = 2
    # Calculate a better upper bound
    right = min(target_number - 1, int(math.pow(target_number, 1.0/num_additional_terms)) + 1)
    # ... rest of binary search

3. Edge Case: n = "3"

The code returns str(target_number - 1) for the case when no base is found, which gives "2" for n = "3". This is correct (3 in base 2 is "11"), but the logic might not be immediately clear.

Documentation improvement:

# Special case: when m = 1 (two digits "11" in base k)
# We have: n = 1 + k, so k = n - 1
# This is always valid for n > 2
# Example: 3 = 1 + 2, so 3 in base 2 = "11"
return str(target_number - 1)

4. Precision Issues with Large Numbers

When dealing with very large values of n, floating-point arithmetic (if used for optimization) can introduce precision errors.

Solution: Stick to integer arithmetic throughout the calculation and avoid using math.pow() or other floating-point operations for the actual validation:

# Avoid this:
# if math.pow(base, num_terms + 1) - 1 == target_number * (base - 1):

# Use this instead:
if calculate_sum_of_powers(base, num_terms) == target_number:
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

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

Load More