Taro Logo

Range Product Queries of Powers

Medium
Asked by:
Profile picture
Profile picture
14 views
Topics:
ArraysBit Manipulation

Given a positive integer n, there exists a 0-indexed array called powers, composed of the minimum number of powers of 2 that sum to n. The array is sorted in non-decreasing order, and there is only one way to form the array.

You are also given a 0-indexed 2D integer array queries, where queries[i] = [lefti, righti]. Each queries[i] represents a query where you have to find the product of all powers[j] with lefti <= j <= righti.

Return an array answers, equal in length to queries, where answers[i] is the answer to the ith query. Since the answer to the ith query may be too large, each answers[i] should be returned modulo 109 + 7.

Example 1:

Input: n = 15, queries = [[0,1],[2,2],[0,3]]
Output: [2,4,64]
Explanation:
For n = 15, powers = [1,2,4,8]. It can be shown that powers cannot be a smaller size.
Answer to 1st query: powers[0] * powers[1] = 1 * 2 = 2.
Answer to 2nd query: powers[2] = 4.
Answer to 3rd query: powers[0] * powers[1] * powers[2] * powers[3] = 1 * 2 * 4 * 8 = 64.
Each answer modulo 109 + 7 yields the same answer, so [2,4,64] is returned.

Example 2:

Input: n = 2, queries = [[0,0]]
Output: [2]
Explanation:
For n = 2, powers = [2].
The answer to the only query is powers[0] = 2. The answer modulo 109 + 7 is the same, so [2] is returned.

Constraints:

  • 1 <= n <= 109
  • 1 <= queries.length <= 105
  • 0 <= starti <= endi < powers.length

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the constraints on the input integer 'n', and the size of the 'queries' array?
  2. Can 'start' and 'end' indices in the 'queries' array be equal, and what should the result be in that case?
  3. Is the 'queries' array guaranteed to have valid 'start' and 'end' indices within the range of the powers of 2 array?
  4. Could you clarify the expected behavior if 'n' is zero, negative, or cannot be represented as the sum of powers of 2?
  5. Can you confirm the data type of the 'start' and 'end' indices in the 'queries' array (e.g., are they integers)?

Brute Force Solution

Approach

We're given a number and a bunch of start and end points. Our goal is to find the product of specific powers of two, based on ranges defined by those start and end points. The brute force method directly calculates the product for each range separately by going through each number within the range and multiplying the appropriate powers of two.

Here's how the algorithm would work step-by-step:

  1. First, figure out which powers of two are present in the given number.
  2. For each start and end point, we will calculate the product within that range.
  3. For a given range, examine each number from the start point up to the end point.
  4. If a power of two falls inside the current range, include it in the product we are calculating.
  5. Multiply all the powers of two found within the range together to get the product for that specific range.
  6. Since the product can be very large, take the remainder after dividing by a specific number to keep the result manageable.
  7. Repeat the above steps for all start and end point pairs to find all the products.

Code Implementation

def range_product_queries_powers_brute_force(number, queries):
    powers_of_two_indices = []
    power_of_two = 1
    index = 0
    while power_of_two <= number:
        if number & power_of_two:
            powers_of_two_indices.append(index)
        power_of_two <<= 1
        index += 1
    
    results = []
    modulo = 10**9 + 7

    for start_index, end_index in queries:
        current_product = 1

        # Iterate through the powers of two indices
        for power_index in powers_of_two_indices:
            # Check if the current power of two falls within the range
            if start_index <= power_index <= end_index:

                # Multiply current product by 2^power_index and apply modulo
                current_product = (current_product * 2) % modulo

        results.append(current_product)

    return results

Big(O) Analysis

Time Complexity
O(n*m)First, finding the powers of two present in the number 'n' takes O(log n) time, but since this is done only once, it doesn't dominate the overall complexity. The main complexity comes from iterating through 'm' queries. For each query (start, end), we iterate from start to end which, in the worst case, iterates through all 'n' powers of two. Thus, for 'm' queries each running up to 'n' iterations, the time complexity will be O(n*m).
Space Complexity
O(N)The algorithm first identifies the powers of two present in the input number, which requires storing these powers, up to the log base 2 of the number. In the worst-case scenario, where the input number has many powers of two, this step creates an auxiliary array of size N, where N is the number of powers of two less than or equal to the input number. The rest of the algorithm primarily involves arithmetic operations and does not create additional significant data structures relative to N. Therefore, the dominant space complexity is O(N).

Optimal Solution

Approach

The core idea is to first pre-compute and store the powers of the given number within the specified range. Then, for each query, instead of recalculating the product from scratch, use the stored powers to efficiently compute the product of the relevant powers and apply the modulo operation.

Here's how the algorithm would work step-by-step:

  1. Find all the powers of the given number 'n' that are less than or equal to the given 'upper bound'. Store these powers in a separate list.
  2. For each query, get the 'start' and 'end' points of the range.
  3. Compute the product of the powers within the specified range using the powers we saved earlier.
  4. Apply the modulo operation to the final product before returning.

Code Implementation

def range_product_queries_of_powers(number, upper_bound, queries):
    powers = []
    current_power = number

    # Find powers of n within the upper bound.
    while current_power <= upper_bound:
        powers.append(current_power)
        if upper_bound // number < current_power:
            break
        current_power *= number

    results = []
    modulo = 10**9 + 7

    for start_index, end_index in queries:
        product = 1
        # Calculate product of powers within the range.
        for i in range(start_index, end_index + 1):
            product = (product * powers[i]) % modulo

        results.append(product)

    return results

Big(O) Analysis

Time Complexity
O(k + q)First, finding the powers of n involves iterating up to the upper bound, but we only store the powers of n that are less than or equal to it. Let's denote the number of powers found and stored as 'k'. Finding these powers takes O(k) time since we are effectively appending to a list until the power exceeds the upper bound, which is proportional to the number of powers found. Then, for each of the 'q' queries, we iterate through a sub-range of the stored powers list (of size k). The product calculation for each query range is O(1) since only multiplication and modulo operations occur on elements of the precomputed powers. Since each of the q queries takes O(1) to calculate, the time complexity of the queries is O(q). Finally, because we first search for the powers, and then we use the result of that search for all queries, the total time complexity is O(k + q), where k is the number of powers found and q is the number of queries.
Space Complexity
O(log_n(upper_bound))The primary space usage comes from storing the powers of 'n' that are less than or equal to the 'upper bound'. In the worst-case, we could store approximately log_n(upper_bound) powers. No significant additional data structures are created; the space used scales with the number of powers. Therefore, the auxiliary space complexity is O(log_n(upper_bound)).

Edge Cases

n is 0
How to Handle:
Return an empty list of powers since 0 cannot be expressed as a sum of positive powers of 2.
n is 1
How to Handle:
Return a list containing only 1, as 2^0 = 1.
queries array is empty or null
How to Handle:
Return an empty list of results as there are no queries to process.
A query with start index greater than end index
How to Handle:
Swap the start and end indices to ensure a valid range is processed.
start or end index out of bounds for powers array
How to Handle:
Handle the out-of-bounds index by either clipping it to the valid range or returning 1 if out of bounds since it will be part of a product.
Large n causing many powers and potential integer overflow during product calculation
How to Handle:
Apply the modulo operator (10^9 + 7) after each multiplication to prevent integer overflow.
A query range that includes the entire powers array
How to Handle:
The product across the entire array should still be calculated correctly using the modulo operator.
n is a large number which is close to Integer.MAX_VALUE
How to Handle:
Handle this by ensuring that the powers of 2 calculation and subsequent modulo operations can handle large numbers without overflow.