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 integerm ≥ 0
- This means
n
in basek
would be written as "111...1" (all 1's)
For example:
- If
n = 13
, the smallest good base is3
because13
in base3
equals111
(since1×3² + 1×3¹ + 1×3⁰ = 9 + 3 + 1 = 13
) - If
n = 4681
, the smallest good base is8
because4681 = 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.
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:
-
Convert the string
n
to an integernum
for numerical operations. -
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
-
For each
m
, use binary search to find the basek
:- 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
)
- Calculate midpoint:
- Set search range:
-
After binary search converges to
l
, check ifcal(l, m) == num
:- If yes, we found an exact match! Return
str(l)
as the smallest good base for thism
- If no, continue to the next smaller
m
- If yes, we found an exact match! Return
-
If no base is found for any
m >= 2
, returnstr(num - 1)
:- This handles the case where
m = 1
, meaningn = 1 + k
- Therefore
k = n - 1
, andn
in base(n-1)
equals11
- This handles the case where
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 EvaluatorExample 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
, butcal(2, m) > 13
for all thesem
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 form = 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:
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
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!