
Problem Statement
The problem requires the development of an algorithm to manage a real-time data stream of integers and enabling the calculation of the product of the last k numbers in the stream, at any point in time. The central component to implement is the ProductOfNumbers class, which offers two functionalities: adding numbers to the stream and retrieving the product of the recent k numbers.
This class is initialized empty and expands as integers are added. Using the add(int num) method, integers are appended to the current stream. The getProduct(int k) method then provides a way to compute the product of the last k integers within this stream. It’s ensured through constraints that the number of elements in the stream always satisfies the requests for k. Additionally, the result of any product computation will always remain bounded by the size of a 32-bit integer, guaranteeing no overflow errors.
Examples
Example 1
Input:
["ProductOfNumbers","add","add","add","add","add","getProduct","getProduct","getProduct","add","getProduct"]
Input:
[[],[3],[0],[2],[5],[4],[2],[3],[4],[8],[2]]
Output:
[null,null,null,null,null,null,20,40,0,null,32]
Explanation:
ProductOfNumbers productOfNumbers = new ProductOfNumbers(); productOfNumbers.add(3); // [3] productOfNumbers.add(0); // [3,0] productOfNumbers.add(2); // [3,0,2] productOfNumbers.add(5); // [3,0,2,5] productOfNumbers.add(4); // [3,0,2,5,4] productOfNumbers.getProduct(2); // return 20. The product of the last 2 numbers is 5 * 4 = 20 productOfNumbers.getProduct(3); // return 40. The product of the last 3 numbers is 2 * 5 * 4 = 40 productOfNumbers.getProduct(4); // return 0. The product of the last 4 numbers is 0 * 2 * 5 * 4 = 0 productOfNumbers.add(8); // [3,0,2,5,4,8] productOfNumbers.getProduct(2); // return 32. The product of the last 2 numbers is 4 * 8 = 32
Constraints
0 <= num <= 1001 <= k <= 4 * 10^4- At most
4 * 10^4calls will be made toaddandgetProduct. - The product of the stream at any point in time will fit in a 32-bit integer.
Approach and Intuition
The goal is to continuously support two operations efficiently: adding an integer to a list and computing the product of the last k integers of the list. If we were to handle this naively, i.e., calculating the product on-demand each time the getProduct(k) is called, it would lead to inefficient computations, especially as the size of the stream grows and k becomes large.
Instead, consider some strategies and observations:
Efficient Storage with Prefix Products: Instead of storing integers directly, store their prefix product (product of all numbers up to that index). Initially, this list could just contain an initialized value of
1to simplify multiplicative calculations. On eachadd(num), compute a new prefix product by multiplying the last item in the prefix product list by the newnum. Ifnumis zero, reset the list and start over because the product of any list that includes this zero will be zero until a new number is added.Managing Zeros: Zeros in the stream complicate the computation since any number multiplied by zero is zero. Reset the prefix product list each time a zero is encountered. Maintain a count of how many elements are in the current valid segment of non-zero numbers.
Computing the Product for
getProduct(k): Ifkis greater than the number of valid (non-zero reset) elements added since the last zero, return 0. Otherwise, the product of the lastkelements is:prefix[-1] / prefix[-k-1]
This approach ensures that each add and getProduct operation runs in constant time O(1), making it highly efficient even for the upper bound of 40,000 operations.
Solutions
- C++
class NumberMultiplier {
private:
vector<int> cumulativeProduct;
int currentSize = 0;
public:
NumberMultiplier() {
cumulativeProduct.push_back(1);
currentSize = 0;
}
void addNumber(int value) {
if (value == 0) {
cumulativeProduct = {1};
currentSize = 0;
} else {
cumulativeProduct.push_back(cumulativeProduct[currentSize] * value);
currentSize++;
}
}
int getProductOfLastK(int k) {
if (k > currentSize) return 0;
return cumulativeProduct[currentSize] / cumulativeProduct[currentSize - k];
}
};
This solution involves a C++ class NumberMultiplier designed to efficiently compute the product of the last k numbers added to it. The class uses a private vector cumulativeProduct to store the cumulative products, allowing O(1) time complexity for retrieving the product of any last k elements. The implementation includes the following components:
Constructor
NumberMultiplier(): Initializes thecumulativeProductwith 1 and setscurrentSizeto 0. This setup helps handle the edge case when no numbers are added or the product of any numbers including zero should be zero.Method
addNumber(int value): Adds a number to the stream. If the number is zero, it resets thecumulativeProductandcurrentSizeas any product including zero will result in zero. If not, it calculates the new cumulative product and adds it tocumulativeProduct, then incrementscurrentSize.Method
getProductOfLastK(int k): Retrieves the product of the lastknumbers. It checks ifkis greater thancurrentSize. If so, it returns 0, as there aren't enough numbers to form the product. If not, it computes the product by dividing the current cumulative product by the product at the positioncurrentSize - k.
Use the addNumber method to add numbers to the buffer and the getProductOfLastK method to retrieve the product of the last k numbers dynamically and efficiently. This approach avoids direct product calculations, which could be inefficient and prone to integer overflows for large inputs.
- Java
class ProductStream {
private ArrayList<Integer> productSeries = new ArrayList<>();
private int currentSize = 0;
public ProductStream() {
productSeries.add(1);
currentSize = 0;
}
public void append(int value) {
if (value == 0) {
productSeries = new ArrayList<Integer>();
productSeries.add(1);
currentSize = 0;
} else {
productSeries.add(productSeries.get(currentSize) * value);
currentSize++;
}
}
public int getLastKProduct(int k) {
if (k > currentSize) return 0;
return productSeries.get(currentSize) / productSeries.get(currentSize - k);
}
}
The provided Java solution is a class named ProductStream that manages a series of products derived from a stream of numbers. Here's how it efficiently calculates the product of the last k numbers:
- Implement the
ProductStreamclass with a privateArrayList<Integer>namedproductSeriesto store cumulative products, and acurrentSizevariable to keep track of the size of the ArrayList. - The constructor initializes
productSeriesby adding a1to handle multiplication effectively and setscurrentSizeto0. - The
append(int value)method handles new numbers being added to the stream:- If the number is
0, it resets theproductSeriesas any number multiplied by zero is zero, affecting subsequent products. It reinitializes the list with1and resetscurrentSizeto0. - If the number is not zero, it appends a new product to
productSeriesby multiplying the latest value by the last number's cumulative product. It then incrementscurrentSize.
- If the number is
- The
getLastKProduct(int k)method returns the product of the lastkentries:- If
kis greater thancurrentSize, no sufficient numbers exist, hence it returns0. - Otherwise, it calculates the product by dividing the most recent stored product by the product stored
kplaces back in the list.
- If
This efficient approach leverages cumulative multiplication to handle the up-to-date products which eliminates the need to multiply all k numbers each time, thus optimizing the operations when fetching the product of the last k numbers.
- Python
class NumberStreamProduct:
def __init__(self):
# Initialize buffer for products with 1 as the base value
self.cumulative_products = [1]
self.current_length = 0
def append_value(self, value: int):
# Handle special case where current value is 0
if value == 0:
self.cumulative_products = [1]
self.current_length = 0
else:
# Record cumulative product of newly added value
self.cumulative_products.append(self.cumulative_products[self.current_length] * value)
self.current_length += 1
def query_product(self, query_size: int) -> int:
# Guard against invalid query sizes
if query_size > self.current_length:
return 0
# Calculate the product by dividing the total product by the product before the desired range
return (
self.cumulative_products[self.current_length] // self.cumulative_products[self.current_length - query_size]
)
The provided Python code defines a class NumberStreamProduct, which handles a sequence of numbers and allows the calculation of the product of the last k numbers efficiently. Here's how the implementation works:
Initialization: The constructor initializes a list
cumulative_productswith a single element,1, representing the cumulative product initialized at the base.current_lengthis set to0, indicating the current length of the sequence processed.Appending Values:
- The
append_valuemethod adds a new value to the sequence. - If the value is
0, it resetscumulative_productsto[1]andcurrent_lengthto0since any subsequent product containing this zero will be zero. - For any non-zero value, it appends the product of this value and the last element of
cumulative_productstocumulative_products. Thecurrent_lengthis incremented by one.
- The
Querying Product:
- The
query_productmethod returns the product of the lastknumbers. - If the requested
query_sizeis greater thancurrent_length, it returns0as the query size is invalid (exceeds the number of elements in the sequence). - Otherwise, it calculates the product by dividing the last entry in
cumulative_productsby the product recordedquery_sizeelements earlier. This division effectively gives the product of the lastkelements.
- The
This approach is efficient as it avoids recalculating the product from scratch each time, utilizing previously stored results for optimal performance. The use of cumulative products facilitates quick computations, particularly useful when dealing with sequences where frequent product calculations are required.