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:

  1. Determine and strip all trailing zeros from the product. Let the count of these trailing zeros be C.
  2. Find the number of remaining digits, d, in the product (after removing trailing zeros). If d > 10, we must create an abbreviated form by taking the first 5 digits and the last 5 digits of the product. If d <= 10, we do not abbreviate but retain the full number.
  3. 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, and C 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:

  1. 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.

  2. 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 by 1e10 to keep the last 10 digits.

  3. 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 than 1e5. This ensures that the significant digits do not get truncated during multiplication.

  4. Assembling the Result: If the number of digits d in the significant product (after removing trailing zeros by dividing out 2^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.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Which one best describes the time complexity of the following code?

1int factorial(int n) {
2  if (n < 0) {
3    return -1;
4  } else if (n == 0) {
5    return 1;
6  } else {
7    return n * factorial(n - 1);
8  }
9}

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:

  1. Initialize Counters for Trailing Zeros: Before entering the loop, counters cnt2 and cnt5 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 (since 2 * 5 = 10).

  2. First Loop – Count Trailing Zeros: The first loop iterates through all numbers from left to right (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 minimize cnt2 and cnt5, ensuring they are equal.

  3. Initialize Variables for Significant Digits: Variables pre and suf are both set to 1. If the actual product is greater than 1e10, the variable gt is set to True.

  4. 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 obtained cnt2 and cnt5, so as to remove the accumulated trailing zeros. If at any point suf becomes greater than 1e10, which means it has at least 11 digits, it's truncated to its last 10 digits using modulo operation suf %= 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. If pre exceeds 1e5, it's divided repeatedly by 10 to keep only significant digits without trailing decimal places (since pre is considered a floating-point number).

  5. Assembling Abbreviated Product: Once the loops are done, the solution checks whether the digits in suf are more than 10 by looking at gt. If so, it assembles the abbreviation by taking the integer part of pre (as the first few significant digits), 5 last digits of suf (ensuring zero padding using str(suf % int(1e5)).zfill(5)), and appends the exponent part eC. If there are 10 or fewer digits in suf, then the final string is obtained by simply converting suf to a string and appending the exponent part eC.

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.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?

Example 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.

  1. Initialize Counters for Trailing Zeros: We start with cnt2 = 0 and cnt5 = 0.

  2. 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 and cnt5 += 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 and cnt5 = 2. We set both cnt2 and cnt5 to the minimum of the two, which is 2, so C = 2.

  3. Initialize Variables for Significant Digits: We initialize pre = 1 and suf = 1. gt is initialized to False.

  4. Second Loop – Multiply and Maintain Digits: We go through numbers 10 to 15 again, updating suf and pre:

    • Multiply suf by 10. suf becomes 10. No division by 1e10 needed.
    • Multiply suf by 11. suf becomes 110. No division by 1e10 needed.
    • Multiply suf by 12. suf becomes 1320. No division by 1e10 needed.
    • Multiply suf by 13. suf becomes 17160. No division by 1e10 needed.
    • Multiply suf by 14. suf becomes 240240. No division by 1e10 needed.
    • Multiply suf by 15. suf becomes 3603600. No division by 1e10 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 since pre never exceeds 1e5 in this example, no division is needed to keep the significant digits.

  5. Assembling Abbreviated Product: The final value of suf after removing trailing zeros is 36036 (3603600 without the trailing zeros caused by factors of 10). Since suf has less than 10 digits, we don't need to abbreviate it.

    The final result is 36036e2, with e2 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
Not Sure What to Study? Take the 2-min Quiz:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

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.

  1. 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 is right - left + 1. The division and modulo operations within the loops occur at most O(log(x)) times per number, where x is the value being factored. Since log(x) is much smaller than right - left, we can consider the inner operations to be O(1) for each number. Hence, the time complexity for the first loop is approximately O(right - left).

  2. 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 most O(log(pre)) times per number.

    The gt (greater than) condition checks suf >= 1e10 at each iteration, but as soon as suf exceeds 1e10, the modulo operation maintains suf within 1e10, 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 also O(right - left).

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, and c.
  • Prefix and Suffix Values: Constant space is used for pre and suf.
  • 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.

Fast Track Your Learning with Our Quick Skills Quiz:

Given an array of 1,000,000 integers that is almost sorted, except for 2 pairs of integers. Which algorithm is fastest for sorting the array?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫