2117. Abbreviating the Product of a Range
Problem Description
You need to find the product of all integers in the range [left, right]
(inclusive) and represent it in an abbreviated format.
The abbreviation process works as follows:
-
Remove trailing zeros: Calculate the product, then count and remove all trailing zeros. Store this count as
C
.- For example,
1000
has 3 trailing zeros, while546
has 0 trailing zeros.
- For example,
-
Format the remaining digits: After removing trailing zeros, count the remaining digits as
d
.- If
d > 10
: Show only the first 5 digits and last 5 digits, separated by...
- Example:
1234567654321
becomes12345...54321
- Example:
- If
d ≤ 10
: Keep the number as is- Example:
1234567
stays as1234567
- Example:
- If
-
Final representation: Express the result as a string in the format
<number>eC
- Example:
12345678987600000
becomes"12345...89876e5"
(5 trailing zeros removed)
- Example:
The solution approach cleverly handles the large product by:
- Counting factors of 2 and 5 separately to determine trailing zeros (since
10 = 2 × 5
) - Maintaining both prefix and suffix of the product to avoid overflow
- Removing factors of 2 and 5 during multiplication to keep numbers manageable
- Using modulo operations to track the last digits while keeping track of whether the product exceeds 10 digits
The key insight is that trailing zeros come from pairs of factors 2 and 5, so C = min(count of 2s, count of 5s)
. The algorithm tracks the first 5 and last 5 significant digits separately to handle very large products efficiently.
Intuition
The main challenge is that the product of a range of numbers can become astronomically large, making it impossible to compute directly. We need to think about what information we actually need to track.
First, let's understand where trailing zeros come from. A trailing zero appears when we multiply by 10, and 10 = 2 × 5
. So each trailing zero corresponds to a pair of factors 2 and 5 in our product. Since 2s appear more frequently than 5s in most ranges, the number of trailing zeros equals the minimum of the count of 2s and count of 5s.
Now, how do we handle the massive product? The key insight is that we don't need the entire number - we only need:
- The first 5 digits (for the prefix)
- The last 5 digits (for the suffix)
- The count of trailing zeros
For the prefix, we can multiply numbers normally and keep dividing by 10 whenever the result gets too large (beyond 5 digits). This maintains the most significant digits.
For the suffix, we need to be more careful. We want the last 5 non-zero digits. The trick is to:
- Remove factors of 2 and 5 as we multiply (since they contribute to trailing zeros, not the significant digits)
- Keep only the last 10 digits using modulo
10^10
to prevent overflow - At the end, take modulo
10^5
to get the last 5 significant digits
Why remove 2s and 5s during multiplication? If we kept them, our number would grow unnecessarily large with zeros that we'd eventually discard anyway. By removing equal numbers of 2s and 5s (which form 10s), we're essentially doing the "remove trailing zeros" step incrementally during the computation rather than at the end.
The gt
(greater than) flag tracks whether our product ever exceeded 10 digits. If it never did, we don't need the abbreviated format with ...
- we can just return the actual product with trailing zeros removed.
Learn more about Math patterns.
Solution Approach
The implementation follows these key steps:
Step 1: Count factors of 2 and 5
cnt2 = cnt5 = 0
for x in range(left, right + 1):
while x % 2 == 0:
cnt2 += 1
x //= 2
while x % 5 == 0:
cnt5 += 1
x //= 5
We iterate through each number in the range and count total factors of 2 and 5. This determines how many trailing zeros the final product will have.
Step 2: Initialize variables
c = cnt2 = cnt5 = min(cnt2, cnt5)
pre = suf = 1
gt = False
c
stores the number of trailing zeros (minimum of 2s and 5s count)- We reset
cnt2
andcnt5
to this minimum value - we'll decrement these as we remove factors pre
tracks the prefix (first 5 digits)suf
tracks the suffix (last significant digits)gt
flag indicates if the product exceeds 10 digits
Step 3: Calculate prefix and suffix
for x in range(left, right + 1):
suf *= x
while cnt2 and suf % 2 == 0:
suf //= 2
cnt2 -= 1
while cnt5 and suf % 5 == 0:
suf //= 5
cnt5 -= 1
if suf >= 1e10:
gt = True
suf %= int(1e10)
pre *= x
while pre > 1e5:
pre /= 10
For each number:
- Suffix calculation: Multiply
suf
by the current number, then remove pairs of 2s and 5s (which form trailing zeros). We only remove as many as we counted (cnt2
andcnt5
act as counters). Keep only the last 10 digits using modulo to prevent overflow. - Prefix calculation: Multiply
pre
by the current number. Whenever it exceeds 5 digits, divide by 10 to maintain only the most significant 5 digits. - Set
gt = True
if suffix ever reaches or exceeds10^10
, indicating the product has more than 10 significant digits.
Step 4: Format the result
if gt:
return str(int(pre)) + "..." + str(suf % int(1e5)).zfill(5) + "e" + str(c)
return str(suf) + "e" + str(c)
- If the product exceeded 10 digits (
gt == True
): Return the abbreviated format with first 5 digits, "...", last 5 digits (usingzfill(5)
to ensure 5 digits with leading zeros if needed), and the exponent notation. - Otherwise: Return the actual product (with trailing zeros removed) followed by the exponent notation.
The clever part of this algorithm is maintaining both prefix and suffix simultaneously while removing trailing zero factors on-the-fly, allowing us to handle products that would otherwise cause integer overflow.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the range [12, 19]
.
Step 1: Count factors of 2 and 5
Going through each number to count prime factors:
- 12 = 2² × 3 → contributes 2 twos
- 13 = 13 → contributes 0 twos, 0 fives
- 14 = 2 × 7 → contributes 1 two
- 15 = 3 × 5 → contributes 1 five
- 16 = 2⁴ → contributes 4 twos
- 17 = 17 → contributes 0 twos, 0 fives
- 18 = 2 × 3² → contributes 1 two
- 19 = 19 → contributes 0 twos, 0 fives
Total: cnt2 = 8
, cnt5 = 1
Trailing zeros: c = min(8, 1) = 1
Step 2: Initialize variables
c = 1
(one trailing zero)cnt2 = 1
,cnt5 = 1
(reset to minimum, will decrement as we remove factors)pre = 1
,suf = 1
(start products)gt = False
(hasn't exceeded 10 digits yet)
Step 3: Calculate prefix and suffix
Processing each number:
x | suf before | suf after multiply | Remove 2s/5s | suf final | pre |
---|---|---|---|---|---|
12 | 1 | 12 | 12÷2=6 (cnt2→0) | 6 | 12 |
13 | 6 | 78 | no factors | 78 | 156 |
14 | 78 | 1092 | no 2s left | 1092 | 2184 |
15 | 1092 | 16380 | 16380÷5=3276 (cnt5→0) | 3276 | 32760 |
16 | 3276 | 52416 | no factors left | 52416 | 52416 |
17 | 52416 | 891072 | no factors left | 891072 | 89107 (÷10) |
18 | 891072 | 16039296 | no factors left | 16039296 | 16039 (÷100) |
19 | 16039296 | 304746624 | no factors left | 304746624 | 30474 (÷10) |
Note: When pre > 100000
, we divide by 10 to keep only 5 most significant digits.
The actual product is 12×13×14×15×16×17×18×19 = 304,746,240 (with one trailing zero from the 2×5 pair).
Step 4: Format the result
Since suf = 304746624
never exceeded 10¹⁰ and has 9 digits (less than 10), gt
remains False
.
After removing the trailing zero factor (one pair of 2×5), we had suf = 30474624
.
Since gt = False
, we return: "30474624e1"
This matches our expectation: the product 304,746,240 has 1 trailing zero, and after removing it we get 30,474,624.
Another example with abbreviation: [25, 30]
Quick calculation shows this product is 25×26×27×28×29×30 = 427,518,000.
- Factors: 25=5², 26=2×13, 27=3³, 28=2²×7, 29=29, 30=2×3×5
- Total:
cnt2 = 4
,cnt5 = 3
, soc = 3
trailing zeros - After removing 3 pairs of (2×5), the significant part is 427,518
- Since this has only 6 digits (≤10), no abbreviation needed
- Result:
"427518e3"
For a range that would produce abbreviation (like [2, 100]), the product would have many more than 10 significant digits, triggering the gt
flag and producing a result like "28242...28864e24"
.
Solution Implementation
1class Solution:
2 def abbreviateProduct(self, left: int, right: int) -> str:
3 # Count total factors of 2 and 5 in the product range
4 count_twos = count_fives = 0
5 for num in range(left, right + 1):
6 temp = num
7 # Count factors of 2
8 while temp % 2 == 0:
9 count_twos += 1
10 temp //= 2
11 # Count factors of 5
12 while temp % 5 == 0:
13 count_fives += 1
14 temp //= 5
15
16 # Number of trailing zeros equals minimum of 2s and 5s
17 trailing_zeros = min(count_twos, count_fives)
18
19 # Reset counters to remove equal pairs of 2s and 5s
20 remaining_twos = remaining_fives = trailing_zeros
21
22 # Track prefix (first digits) and suffix (last digits)
23 prefix = 1
24 suffix = 1
25 overflow_occurred = False
26
27 # Calculate the product while removing factors of 10
28 for num in range(left, right + 1):
29 # Update suffix with current number
30 suffix *= num
31
32 # Remove factors of 2 up to the number of trailing zeros
33 while remaining_twos > 0 and suffix % 2 == 0:
34 suffix //= 2
35 remaining_twos -= 1
36
37 # Remove factors of 5 up to the number of trailing zeros
38 while remaining_fives > 0 and suffix % 5 == 0:
39 suffix //= 5
40 remaining_fives -= 1
41
42 # Check if suffix exceeds 10^10 (overflow detection)
43 if suffix >= 10**10:
44 overflow_occurred = True
45 suffix %= 10**10 # Keep last 10 digits
46
47 # Update prefix with current number
48 prefix *= num
49
50 # Keep prefix around 5 digits by dividing by 10
51 while prefix > 10**5:
52 prefix /= 10
53
54 # Format the result based on whether overflow occurred
55 if overflow_occurred:
56 # Format: first_5_digits...last_5_digits_e_trailing_zeros
57 first_digits = str(int(prefix))
58 last_digits = str(suffix % 10**5).zfill(5)
59 return f"{first_digits}...{last_digits}e{trailing_zeros}"
60 else:
61 # Format: exact_value_e_trailing_zeros
62 return f"{suffix}e{trailing_zeros}"
63
1class Solution {
2
3 public String abbreviateProduct(int left, int right) {
4 // Count factors of 2 and 5 in the product range
5 int countFactorsOf2 = 0;
6 int countFactorsOf5 = 0;
7
8 // Extract all factors of 2 and 5 from each number in range
9 for (int num = left; num <= right; num++) {
10 int temp = num;
11
12 // Count factors of 2
13 while (temp % 2 == 0) {
14 countFactorsOf2++;
15 temp /= 2;
16 }
17
18 // Count factors of 5
19 while (temp % 5 == 0) {
20 countFactorsOf5++;
21 temp /= 5;
22 }
23 }
24
25 // The number of trailing zeros equals min(count of 2s, count of 5s)
26 int trailingZeros = Math.min(countFactorsOf2, countFactorsOf5);
27
28 // Remove paired factors of 2 and 5 (they create trailing zeros)
29 int remainingFactorsOf2 = trailingZeros;
30 int remainingFactorsOf5 = trailingZeros;
31
32 // Track the suffix (last digits) and prefix (first digits) of the product
33 long suffixDigits = 1;
34 double prefixDigits = 1;
35 boolean hasOverflow = false;
36
37 // Calculate the product while removing factors of 2 and 5
38 for (int num = left; num <= right; num++) {
39 // Multiply suffix by current number
40 suffixDigits *= num;
41
42 // Remove factors of 2 from suffix
43 while (remainingFactorsOf2 > 0 && suffixDigits % 2 == 0) {
44 suffixDigits /= 2;
45 remainingFactorsOf2--;
46 }
47
48 // Remove factors of 5 from suffix
49 while (remainingFactorsOf5 > 0 && suffixDigits % 5 == 0) {
50 suffixDigits /= 5;
51 remainingFactorsOf5--;
52 }
53
54 // Check if product exceeds 10^10 (need abbreviation)
55 if (suffixDigits >= (long) 1e10) {
56 hasOverflow = true;
57 // Keep only last 10 digits
58 suffixDigits %= (long) 1e10;
59 }
60
61 // Calculate prefix digits (first 5 digits)
62 prefixDigits *= num;
63 // Keep prefix manageable by dividing when it exceeds 10^5
64 while (prefixDigits > 1e5) {
65 prefixDigits /= 10;
66 }
67 }
68
69 // Format the result based on whether abbreviation is needed
70 if (hasOverflow) {
71 // Format: [first 5 digits]...[last 5 digits]e[trailing zeros]
72 int firstFiveDigits = (int) prefixDigits;
73 int lastFiveDigits = (int) (suffixDigits % (int) 1e5);
74 return firstFiveDigits + "..." + String.format("%05d", lastFiveDigits) + "e" + trailingZeros;
75 } else {
76 // No abbreviation needed, just append trailing zeros count
77 return suffixDigits + "e" + trailingZeros;
78 }
79 }
80}
81
1class Solution {
2public:
3 string abbreviateProduct(int left, int right) {
4 // Count total factors of 2 and 5 in the product range
5 int factorsOfTwo = 0, factorsOfFive = 0;
6 for (int num = left; num <= right; ++num) {
7 int temp = num;
8 // Count factors of 2 in current number
9 while (temp % 2 == 0) {
10 ++factorsOfTwo;
11 temp /= 2;
12 }
13 // Count factors of 5 in current number
14 while (temp % 5 == 0) {
15 ++factorsOfFive;
16 temp /= 5;
17 }
18 }
19
20 // Number of trailing zeros equals min(factors of 2, factors of 5)
21 int trailingZeros = min(factorsOfTwo, factorsOfFive);
22 factorsOfTwo = factorsOfFive = trailingZeros;
23
24 // Track last significant digits (suffix) and first significant digits (prefix)
25 long long suffix = 1;
26 long double prefix = 1;
27 bool hasOverflow = false;
28
29 // Calculate the product while removing trailing zeros
30 for (int num = left; num <= right; ++num) {
31 suffix *= num;
32
33 // Remove factors of 2 from suffix
34 while (factorsOfTwo > 0 && suffix % 2 == 0) {
35 suffix /= 2;
36 --factorsOfTwo;
37 }
38
39 // Remove factors of 5 from suffix
40 while (factorsOfFive > 0 && suffix % 5 == 0) {
41 suffix /= 5;
42 --factorsOfFive;
43 }
44
45 // Keep only last 10 digits if overflow occurs
46 if (suffix >= 1e10) {
47 hasOverflow = true;
48 suffix %= (long long)1e10;
49 }
50
51 // Maintain prefix at most 5 significant digits
52 prefix *= num;
53 while (prefix > 1e5) {
54 prefix /= 10;
55 }
56 }
57
58 // Format output based on whether overflow occurred
59 if (hasOverflow) {
60 // Product is too large: show first 5 digits...last 5 digits
61 char buffer[10];
62 snprintf(buffer, sizeof(buffer), "%05lld", suffix % (int)1e5);
63 return to_string((int)prefix) + "..." + string(buffer) + "e" + to_string(trailingZeros);
64 }
65
66 // Product fits: show complete non-zero part
67 return to_string(suffix) + "e" + to_string(trailingZeros);
68 }
69};
70
1function abbreviateProduct(left: number, right: number): string {
2 // Count total factors of 2 and 5 in the product range
3 let factorsOfTwo = 0;
4 let factorsOfFive = 0;
5
6 for (let num = left; num <= right; num++) {
7 let temp = num;
8
9 // Count factors of 2 in current number
10 while (temp % 2 === 0) {
11 factorsOfTwo++;
12 temp = Math.floor(temp / 2);
13 }
14
15 // Count factors of 5 in current number
16 while (temp % 5 === 0) {
17 factorsOfFive++;
18 temp = Math.floor(temp / 5);
19 }
20 }
21
22 // Number of trailing zeros equals min(factors of 2, factors of 5)
23 const trailingZeros = Math.min(factorsOfTwo, factorsOfFive);
24 factorsOfTwo = trailingZeros;
25 factorsOfFive = trailingZeros;
26
27 // Track last significant digits (suffix) and first significant digits (prefix)
28 let suffix = 1;
29 let prefix = 1;
30 let hasOverflow = false;
31
32 // Calculate the product while removing trailing zeros
33 for (let num = left; num <= right; num++) {
34 suffix *= num;
35
36 // Remove factors of 2 from suffix
37 while (factorsOfTwo > 0 && suffix % 2 === 0) {
38 suffix = Math.floor(suffix / 2);
39 factorsOfTwo--;
40 }
41
42 // Remove factors of 5 from suffix
43 while (factorsOfFive > 0 && suffix % 5 === 0) {
44 suffix = Math.floor(suffix / 5);
45 factorsOfFive--;
46 }
47
48 // Keep only last 10 digits if overflow occurs
49 if (suffix >= 1e10) {
50 hasOverflow = true;
51 suffix = suffix % 1e10;
52 }
53
54 // Maintain prefix at most 5 significant digits
55 prefix *= num;
56 while (prefix > 1e5) {
57 prefix = prefix / 10;
58 }
59 }
60
61 // Format output based on whether overflow occurred
62 if (hasOverflow) {
63 // Product is too large: show first 5 digits...last 5 digits
64 const last5Digits = (suffix % 1e5).toString().padStart(5, '0');
65 const first5Digits = Math.floor(prefix).toString();
66 return `${first5Digits}...${last5Digits}e${trailingZeros}`;
67 }
68
69 // Product fits: show complete non-zero part
70 return `${suffix}e${trailingZeros}`;
71}
72
Time and Space Complexity
Time Complexity: O(n * log(max_value))
The algorithm consists of two main passes through the range [left, right]
:
-
First loop: Counts factors of 2 and 5 in each number from
left
toright
. For each numberx
, the while loops extract all factors of 2 and 5. In the worst case, a number could haveO(log x)
factors of 2 or 5. Since we iterate throughn = right - left + 1
numbers, this takesO(n * log(max_value))
time. -
Second loop: Iterates through all numbers from
left
toright
again, performing:- Multiplication operations
- Division operations to remove factors of 2 and 5 (bounded by the total count from the first loop)
- Division operations to keep
pre
within bounds (at mostO(log x)
divisions per number)
This loop also takes
O(n * log(max_value))
time.
Overall time complexity: O(n * log(max_value))
where n = right - left + 1
and max_value
is the maximum value in the range.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Integer variables:
cnt2
,cnt5
,c
,x
- Numeric variables:
pre
,suf
- Boolean variable:
gt
All variables store single values regardless of the input size, resulting in O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow When Computing the Product
Pitfall: Directly calculating the product of all numbers in the range will cause integer overflow for large ranges. For example, 100! ≈ 9.3 × 10^157
, which far exceeds the maximum integer limit in most programming languages.
Solution: The algorithm avoids this by:
- Tracking prefix and suffix separately instead of computing the full product
- Removing factors of 2 and 5 immediately during multiplication to keep numbers smaller
- Using modulo operations to maintain only the necessary digits
2. Incorrect Handling of Suffix Leading Zeros
Pitfall: When the suffix has leading zeros after taking modulo, they might be lost. For example, if the last 5 significant digits are 00123
, simply converting to string would give "123"
instead of "00123"
.
Solution: Use zfill(5)
when formatting the suffix to ensure exactly 5 digits with leading zeros if necessary:
last_digits = str(suffix % 10**5).zfill(5) # Ensures "00123" not "123"
3. Premature Factor Removal
Pitfall: Removing ALL factors of 2 and 5 from each number individually would lose information about the actual product. We need to remove only as many factors as there are trailing zeros.
Solution: Use counters (remaining_twos
and remaining_fives
) initialized to the minimum count, and decrement them as factors are removed:
while remaining_twos > 0 and suffix % 2 == 0: suffix //= 2 remaining_twos -= 1
4. Floating Point Precision Loss in Prefix
Pitfall: Using floating-point division for the prefix (prefix /= 10
) can accumulate rounding errors over many iterations, potentially giving incorrect first digits.
Solution: For better precision, consider using integer arithmetic with explicit tracking of the scale, or accept small rounding errors for the first few digits (which is usually acceptable since we only need 5 significant digits).
5. Incorrect Overflow Detection
Pitfall: Only checking if the final result exceeds 10 digits is insufficient. The product might exceed 10 digits during intermediate calculations but then be reduced by factor removal.
Solution: Set the overflow flag (gt
or overflow_occurred
) as soon as the suffix reaches 10^10
, and never unset it:
if suffix >= 10**10: overflow_occurred = True # Once set, stays True suffix %= 10**10
6. Off-by-One Errors in Range
Pitfall: Forgetting that the range [left, right]
is inclusive on both ends, leading to missing the last number.
Solution: Always use range(left, right + 1)
to include both endpoints in the calculation.
A heap is a ...?
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!