2117. Abbreviating the Product of a Range
Problem Description
In this problem, we are given two integer values left
and right
such that left <= right
. We have to calculate the product of all integers in the inclusive range [left, right]
. Then, we must abbreviate the resulting product according to specific rules:
- Determine and strip all trailing zeros from the product. Let the count of these trailing zeros be
C
. - Find the number of remaining digits,
d
, in the product (after removing trailing zeros). Ifd > 10
, we must create an abbreviated form by taking the first 5 digits and the last 5 digits of the product. Ifd <= 10
, we do not abbreviate but retain the full number. - The final representation of the product will be in the form of a string
"<pre>...<suf>eC"
where<pre>
is the first 5 digits,<suf>
is the last 5 digits after removing trailing zeros, andC
is the count of trailing zeros removed.
The task is to return this abbreviated string representation of the product.
Intuition
Let's construct the solution approach step by step:
-
Trailing Zeros: First, count the number of trailing zeros (
C
) by counting how many times 2 and 5 can divide the numbers in the given range. Every pair of 2 and 5 contributes to one trailing zero since 2 * 5 = 10. We only need to find the minimum count of 2s or 5s because the larger count does not contribute to additional zeros. -
Multiplication Without Overflow: To avoid overflow when calculating the product of a possibly large range of numbers, we only preserve the last 10 digits while multiplying. Whenever the product becomes greater than
1e10
, we mod the product by1e10
to keep the last 10 digits. -
Preserving Significant Digits: In order to display the first 5 digits of the product accurately, we maintain another product (
pre
) which we keep scaling down by dividing by 10 whenever it grows larger than1e5
. This ensures that the significant digits do not get truncated during multiplication. -
Assembling the Result: If the number of digits
d
in the significant product (after removing trailing zeros by dividing out2^C * 5^C
) is more than 10, we need to form the string in"<pre>...<suf>eC"
format. Otherwise, we simply return the product followed by"eC"
.
The code provided implements the above logic by maintaining two variables pre
and suf
for the significant product before and after applying modulo, respectively. It handles the counting of trailing zeros (cnt2
for 2s and cnt5
for 5s) and the tracking whether the product has exceeded the 10 digits (gt
flag).
In summary, the solution is designed to calculate and manipulate the necessary parts of the product intelligently to create the abbreviated form that fits the problem constraints, avoiding the computational impossibility of calculating the actual full product of a large sequence of numbers.
Learn more about Math patterns.
Solution Approach
In the given solution, a smart approach is adopted to efficiently calculate a product that could be astronomically large without actual multiplication of all the numbers. Here's a step-by-step walkthrough of the implementation:
-
Initialize Counters for Trailing Zeros: Before entering the loop, counters
cnt2
andcnt5
are initialized to zero. These counters hold the number of times the product can be divided by 2 and 5, respectively. This is important because every pair of(2, 5)
contributes to a trailing zero (since2 * 5 = 10
). -
First Loop – Count Trailing Zeros: The first loop iterates through all numbers from
left
toright
(both inclusive). For each number, the code has two nested while-loops. The first while loop counts how many times the number can be evenly divided by 2, and the second does the same for 5. Whichever factor is less contributes to the count of trailing zeros. After the loop,C
holds the count of trailing zeros, and it is also used to minimizecnt2
andcnt5
, ensuring they are equal. -
Initialize Variables for Significant Digits: Variables
pre
andsuf
are both set to 1. If the actual product is greater than1e10
, the variablegt
is set to True. -
Second Loop – Multiply and Maintain Digits: In the second loop,
suf
is multiplied by each number while continually checking and removing factors of 2 and 5, based on the previously obtainedcnt2
andcnt5
, so as to remove the accumulated trailing zeros. If at any pointsuf
becomes greater than1e10
, which means it has at least 11 digits, it's truncated to its last 10 digits using modulo operationsuf %= int(1e10)
.Simultaneously, the
pre
variable, which is responsible for the significant digits at the start of the product, is multiplied by the same number. Ifpre
exceeds1e5
, it's divided repeatedly by 10 to keep only significant digits without trailing decimal places (sincepre
is considered a floating-point number). -
Assembling Abbreviated Product: Once the loops are done, the solution checks whether the digits in
suf
are more than 10 by looking atgt
. If so, it assembles the abbreviation by taking the integer part ofpre
(as the first few significant digits), 5 last digits ofsuf
(ensuring zero padding usingstr(suf % int(1e5)).zfill(5)
), and appends the exponent parteC
. If there are 10 or fewer digits insuf
, then the final string is obtained by simply convertingsuf
to a string and appending the exponent parteC
.
These steps ensure that a very large product is abbreviated to a string that represents its scale without actually computing or storing the massive number itself, staying within the constraints of practical memory and computational limits.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's go through an example with left = 10
and right = 15
. We want to calculate the product of numbers 10 through 15 and return its abbreviated form following the rules described.
-
Initialize Counters for Trailing Zeros: We start with
cnt2 = 0
andcnt5 = 0
. -
First Loop – Count Trailing Zeros: For each number from 10 to 15:
- 10 can be divided by 2 once and by 5 once.
cnt2 += 1
andcnt5 += 1
. - 11 cannot be divided by 2 or 5, so no change.
- 12 can be divided by 2 twice but not by 5.
cnt2 += 2
. - 13 cannot be divided by 2 or 5, so no change.
- 14 can be divided by 2 once but not by 5.
cnt2 += 1
. - 15 can be divided by 5 once but not by 2.
cnt5 += 1
.
After the loop, we have
cnt2 = 4
andcnt5 = 2
. We set bothcnt2
andcnt5
to the minimum of the two, which is 2, soC = 2
. - 10 can be divided by 2 once and by 5 once.
-
Initialize Variables for Significant Digits: We initialize
pre = 1
andsuf = 1
.gt
is initialized to False. -
Second Loop – Multiply and Maintain Digits: We go through numbers 10 to 15 again, updating
suf
andpre
:- Multiply
suf
by 10.suf
becomes 10. No division by1e10
needed. - Multiply
suf
by 11.suf
becomes 110. No division by1e10
needed. - Multiply
suf
by 12.suf
becomes 1320. No division by1e10
needed. - Multiply
suf
by 13.suf
becomes 17160. No division by1e10
needed. - Multiply
suf
by 14.suf
becomes 240240. No division by1e10
needed. - Multiply
suf
by 15.suf
becomes 3603600. No division by1e10
needed.
During these operations, we keep removing factors of 2 and 5 when necessary to counteract the trailing zeros.
pre
follows the same multiplication process, but sincepre
never exceeds1e5
in this example, no division is needed to keep the significant digits. - Multiply
-
Assembling Abbreviated Product: The final value of
suf
after removing trailing zeros is 36036 (3603600
without the trailing zeros caused by factors of 10). Sincesuf
has less than 10 digits, we don't need to abbreviate it.The final result is
36036e2
, withe2
indicating that we have removed 2 trailing zeros (factors of 10).
Following these steps with our chosen example of 10 to 15, we see the algorithm in action and understand how it performs to avoid any overflows or unnecessary large computations to arrive at the final abbreviated product.
Solution Implementation
1class Solution:
2 def abbreviate_product(self, left: int, right: int) -> str:
3 # Initialize counters for the number of 2's and 5's in the factorization
4 count_two = count_five = 0
5
6 # Factorize numbers between left and right (inclusive) to count 2's and 5's
7 for number in range(left, right + 1):
8 while number % 2 == 0:
9 count_two += 1
10 number //= 2
11 while number % 5 == 0:
12 count_five += 1
13 number //= 5
14
15 # The common factors of 2 and 5 result in trailing zeroes,
16 # which are eventually represented as the power of 10 (exponent 'e').
17 exponent = min(count_two, count_five)
18
19 # Initialize the prefix and suffix for the abbreviated product
20 prefix = suffix = 1
21 exceeds_ten_power_ten = False # Flag to indicate if suffix exceeds 10^10
22
23 for number in range(left, right + 1):
24 # Calculate the suffix of the product
25 suffix *= number
26 while count_two and suffix % 2 == 0:
27 suffix //= 2
28 count_two -= 1
29 while count_five and suffix % 5 == 0:
30 suffix //= 5
31 count_five -= 1
32
33 # If the suffix length exceeds 10^10, truncate and set a flag
34 if suffix >= 1e10:
35 exceeds_ten_power_ten = True
36 suffix %= int(1e10)
37
38 # Calculate the prefix of the product (we only need the first few digits)
39 prefix *= number
40 while prefix > 1e5:
41 prefix /= 10
42
43 # Prepare the result string based on whether the suffix exceeded 10^10
44 if exceeds_ten_power_ten:
45 # Construct the abbreviated form with prefix, ellipsis, suffix, and exponent
46 return f"{int(prefix)}...{str(suffix % int(1e5)).zfill(5)}e{exponent}"
47 else:
48 # If the product does not exceed 10^10, just return the suffix and exponent
49 return f"{suffix}e{exponent}"
50
1class Solution {
2
3 /**
4 * Abbreviates the product of numbers from left to right.
5 *
6 * @param left the starting integer of the range
7 * @param right the ending integer of the range
8 * @return abbreviated string representation of the product
9 */
10 public String abbreviateProduct(int left, int right) {
11 // Count the factors of 2 and 5 in the product
12 int countTwo = 0, countFive = 0;
13 for (int i = left; i <= right; ++i) {
14 int temp = i;
15 while (temp % 2 == 0) { // Count 2s
16 ++countTwo;
17 temp /= 2;
18 }
19 while (temp % 5 == 0) { // Count 5s
20 ++countFive;
21 temp /= 5;
22 }
23 }
24
25 // Only equal amounts of 2s and 5s will contribute to trailing zeros
26 int minCount = Math.min(countTwo, countFive);
27 countTwo = countFive = minCount;
28
29 long suffix = 1; // Holds the last few digits of the product
30 double prefix = 1; // Holds the first few digits of the product
31 boolean greaterThanBound = false; // Indicates if suffix is greater than 1e10 at any point
32
33 for (int i = left; i <= right; ++i) {
34 // Multiply suffix by current number, remove factors of 2 and 5 as needed
35 for (suffix *= i; countTwo > 0 && suffix % 2 == 0; suffix /= 2) {
36 --countTwo;
37 }
38 for (; countFive > 0 && suffix % 5 == 0; suffix /= 5) {
39 --countFive;
40 }
41
42 // If the suffix becomes too large, keep only the last 10 digits
43 if (suffix >= 1e10) {
44 greaterThanBound = true;
45 suffix %= 1e10;
46 }
47
48 // Multiply prefix by current number, keep it within a reasonable size
49 // by removing trailing digits
50 for (prefix *= i; prefix > 1e5; prefix /= 10) {
51 // No operation needed inside the loop
52 }
53 }
54
55 // If the suffix had more than 10 digits at any point, return the abbreviated form
56 if (greaterThanBound) {
57 String formattedSuffix = String.format("%05d", suffix % (int) 1e5);
58 return (int) prefix + "..." + formattedSuffix + "e" + minCount;
59 }
60
61 // If the suffix never exceeded 10 digits, return the product with the number of trailing zeros
62 return suffix + "e" + minCount;
63 }
64}
65
1#include <string>
2#include <algorithm>
3#include <cmath>
4#include <cstdio>
5
6class Solution {
7public:
8 // Abbreviate the product of a range of numbers
9 std::string abbreviateProduct(int left, int right) {
10 int countOfTwo = 0; // Count of factor 2 in the product
11 int countOfFive = 0; // Count of factor 5 in the product
12
13 // Factorize each number in range [left, right] for 2 and 5
14 // and count the factors
15 for (int i = left; i <= right; ++i) {
16 int num = i;
17 while (num % 2 == 0) {
18 num /= 2;
19 ++countOfTwo;
20 }
21 while (num % 5 == 0) {
22 num /= 5;
23 ++countOfFive;
24 }
25 }
26
27 // Determine the number of trailing zeros (equal to min(countOfTwo, countOfFive))
28 int trailingZerosCount = std::min(countOfTwo, countOfFive);
29 countOfTwo = countOfFive = trailingZerosCount;
30
31 long long suffix = 1; // The suffix of the product
32 long double prefix = 1; // The prefix of the product (in scientific notation)
33 bool isLarge = false; // Flag to check if the number is too large
34
35 // Create abbreviated form of the product
36 for (int i = left; i <= right; ++i) {
37 // Multiply suffix and remove trailing 2's and 5's
38 for (suffix *= i; countOfTwo && suffix % 2 == 0; suffix /= 2) {
39 --countOfTwo;
40 }
41 for (; countOfFive && suffix % 5 == 0; suffix /= 5) {
42 --countOfFive;
43 }
44
45 // Check if suffix is greater than 1e10 and adjust it
46 if (suffix >= 1e10) {
47 isLarge = true;
48 suffix %= static_cast<long long>(1e10);
49 }
50 // Adjust prefix while it's larger than 1e5
51 for (prefix *= i; prefix > 1e5; prefix /= 10) {
52 }
53 }
54
55 // If the number is too large, format the output with ellipses
56 if (isLarge) {
57 char buffer[10];
58 std::snprintf(buffer, sizeof(buffer), "%05lld", suffix % static_cast<int>(1e5));
59 return std::to_string(static_cast<int>(prefix)) + "..." + std::string(buffer) + "e" + std::to_string(trailingZerosCount);
60 }
61
62 // If the number is not too large, just output the suffix
63 return std::to_string(suffix) + "e" + std::to_string(trailingZerosCount);
64 }
65};
66
1function countFactors(num: number, divisor: number): number {
2 // Count how many times num is divisible by divisor
3 let count = 0;
4 while (num % divisor === 0) {
5 num /= divisor;
6 ++count;
7 }
8 return count;
9}
10
11// Abbreviate the product of a range of numbers
12function abbreviateProduct(left: number, right: number): string {
13 let countOfTwo = 0; // Count of factor 2 in the product
14 let countOfFive = 0; // Count of factor 5 in the product
15
16 // Factorize each number in range [left, right] for 2 and 5
17 // and count the factors
18 for (let i = left; i <= right; ++i) {
19 countOfTwo += countFactors(i, 2);
20 countOfFive += countFactors(i, 5);
21 }
22
23 // Determine the number of trailing zeros (equal to min(countOfTwo, countOfFive))
24 let trailingZerosCount = Math.min(countOfTwo, countOfFive);
25 countOfTwo -= trailingZerosCount;
26 countOfFive -= trailingZerosCount;
27
28 let suffix: bigint = BigInt(1); // The suffix of the product
29 let prefix: number = 1.0; // The prefix of the product (in scientific notation)
30 let isLarge = false; // Flag to check if the number is too large
31
32 // Create abbreviated form of the product
33 for (let i = left; i <= right; ++i) {
34 suffix *= BigInt(i);
35 while (countOfTwo && suffix % BigInt(2) === BigInt(0)) {
36 suffix /= BigInt(2);
37 --countOfTwo;
38 }
39 while (countOfFive && suffix % BigInt(5) === BigInt(0)) {
40 suffix /= BigInt(5);
41 --countOfFive;
42 }
43 if (suffix >= BigInt(1e10)) {
44 isLarge = true;
45 suffix %= BigInt(1e10);
46 }
47 // Adjust prefix while it's larger than 1e5
48 for (prefix *= i; prefix >= 1e5; prefix /= 10);
49 }
50
51 // If the number is too large, format the output with ellipses
52 if (isLarge) {
53 let formattedSuffix = suffix.toString().padStart(10, '0');
54 let smallSuffix = formattedSuffix.substring(formattedSuffix.length - 5);
55 let smallPrefix = Math.floor(prefix).toString();
56 return `${smallPrefix}...${smallSuffix}e${trailingZerosCount}`;
57 }
58
59 // If the number is not too large, just output the suffix
60 return `${suffix}e${trailingZerosCount}`;
61}
62
Time and Space Complexity
Time Complexity
The time complexity of the given code can be broken down into two main parts: the first loop where the factors of 2 and 5 are counted and the second loop where the prefix and suffix are calculated.
-
First Loop (Counting Factors of 2 and 5): Each number in the range
[left, right]
is examined for factors of 2 and 5. The number of iterations isright - left + 1
. The division and modulo operations within the loops occur at mostO(log(x))
times per number, wherex
is the value being factored. Sincelog(x)
is much smaller thanright - left
, we can consider the inner operations to beO(1)
for each number. Hence, the time complexity for the first loop is approximatelyO(right - left)
. -
Second Loop (Calculating Prefix and Suffix): This loop also spans the range
[left, right]
. Within this loop:- The suffix multiplication and modulo operations occur
right - left + 1
times. - The prefix multiplication occurs
right - left + 1
times as well, but the division to keep it within 1e5 occurs at mostO(log(pre))
times per number.
The
gt
(greater than) condition checkssuf >= 1e10
at each iteration, but as soon assuf
exceeds1e10
, the modulo operation maintainssuf
within1e10
, which is constant time.Therefore, ignoring the smaller cost of divisions to maintain the magnitude of
pre
and the modulo operations (as they are constant time adjustments), the time complexity for the second loop is alsoO(right - left)
. - The suffix multiplication and modulo operations occur
Combining both loops together, the overall time complexity of the function is O(right - left)
.
Space Complexity
The space complexity is determined by the variables used for tracking the count of factors and the prefix and suffix values. No additional data structures that grow with the input size are used.
- Counters: Constant space is used for the counters
cnt2
,cnt5
, andc
. - Prefix and Suffix Values: Constant space is used for
pre
andsuf
. - Boolean Flag: Constant space for the boolean variable
gt
.
Since the space requirement does not scale with the size of the input range [left, right]
, the overall space complexity is O(1)
, which denotes constant space complexity.
Learn more about how to find time and space complexity quickly using problem constraints.
How does merge sort divide the problem into subproblems?
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!