
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 <= 100
1 <= k <= 4 * 10^4
- At most
4 * 10^4
calls will be made toadd
andgetProduct
. - 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
1
to simplify multiplicative calculations. On eachadd(num)
, compute a new prefix product by multiplying the last item in the prefix product list by the newnum
. Ifnum
is 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)
: Ifk
is greater than the number of valid (non-zero reset) elements added since the last zero, return 0. Otherwise, the product of the lastk
elements 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 thecumulativeProduct
with 1 and setscurrentSize
to 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 thecumulativeProduct
andcurrentSize
as 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 lastk
numbers. It checks ifk
is 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
ProductStream
class with a privateArrayList<Integer>
namedproductSeries
to store cumulative products, and acurrentSize
variable to keep track of the size of the ArrayList. - The constructor initializes
productSeries
by adding a1
to handle multiplication effectively and setscurrentSize
to0
. - The
append(int value)
method handles new numbers being added to the stream:- If the number is
0
, it resets theproductSeries
as any number multiplied by zero is zero, affecting subsequent products. It reinitializes the list with1
and resetscurrentSize
to0
. - If the number is not zero, it appends a new product to
productSeries
by 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 lastk
entries:- If
k
is 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
k
places 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_products
with a single element,1
, representing the cumulative product initialized at the base.current_length
is set to0
, indicating the current length of the sequence processed.Appending Values:
- The
append_value
method adds a new value to the sequence. - If the value is
0
, it resetscumulative_products
to[1]
andcurrent_length
to0
since 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_products
tocumulative_products
. Thecurrent_length
is incremented by one.
- The
Querying Product:
- The
query_product
method returns the product of the lastk
numbers. - If the requested
query_size
is greater thancurrent_length
, it returns0
as 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_products
by the product recordedquery_size
elements earlier. This division effectively gives the product of the lastk
elements.
- 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.
No comments yet.