According to the original question source, we are looking for a subarray (substring), which must be contiguous.
To quickly add numbers to a multiset and get the product of the multiset, we can use the data structure of (product of nonzero numbers, number of zeros). This structure takes O(1) space and allows us to do 3 operations (add value, remove value, get product) in O(1) time, assuming numbers are limited to a machine word size, with O(1) multiplication and division time for those words, as supporting arbitrarily large numbers needs higher complexity.
We can create this structure, add L items, and then remove and add one item each iteration to check all other subarrays. This will take O(N) time and O(1) auxiliary space for an input array of length N.
We can also return all possible starting indices to represent all the solutions when there are multiple. This requires O(N-L) space to return the indices.
class QuickProduct:
product = 1
num_zero = 0
def add(self, value):
if value:
self.product *= value
else:
self.num_zero += 1
def remove(self, value):
if value:
self.product //= value
else:
self.num_zero -= 1
def get_product(self):
return 0 if self.num_zero else self.product
def highest_product_num(data, L):
if len(data) < L:
raise ValueError('L must not be smaller than length of data')
q = QuickProduct()
# add first L items
for i in range(L):
q.add(data[i])
best = q.get_product()
start_index = [0]
# try all other possible subarrays
for i in range(L, len(data)):
q.remove(data[i - L])
q.add(data[i])
p = q.get_product()
if best < p:
best = p
start_index = [i - L + 1]
elif best == p:
start_index.append(i - L + 1)
return best, start_index
test_input = [
([4,1,-7,-8,9], 3),
([4,1,-7,-8,9,0,-8,-7,9,-8,-7,9,8,7,8], 3),
([1,3,-6,3,5], 3),
([1,2,3,4,5,6,7,8], 4), ([1,-2,3,4,5,100,2,3,1], 4),
([-10,-10,1,3,2], 4), ([1000,7,-6,2,2],4),
([-1, 0, 1], 2), ([2, 5, 8, 9, 1, 3, 7], 4),
([-1, -1, 2, 1], 2), ([-1000, -1, 2, 3], 2),
([3, 5, 2, 8, 3], 2), ([-1000, -1, 2, 3, 4, 5, 6, 7], 2)
]
for data, L in test_input:
print(data, L, highest_product_num(data, L))
Output:
[4, 1, -7, -8, 9] 3 (504, [2])
[4, 1, -7, -8, 9, 0, -8, -7, 9, -8, -7, 9, 8, 7, 8] 3 (504, [2, 6, 7, 8, 9, 11])
[1, 3, -6, 3, 5] 3 (-18, [0])
[1, 2, 3, 4, 5, 6, 7, 8] 4 (1680, [4])
[1, -2, 3, 4, 5, 100, 2, 3, 1] 4 (6000, [2])
[-10, -10, 1, 3, 2] 4 (300, [0])
[1000, 7, -6, 2, 2] 4 (-168, [1])
[-1, 0, 1] 2 (0, [0, 1])
[2, 5, 8, 9, 1, 3, 7] 4 (720, [0])
[-1, -1, 2, 1] 2 (2, [2])
[-1000, -1, 2, 3] 2 (1000, [0])
[3, 5, 2, 8, 3] 2 (24, [3])
[-1000, -1, 2, 3, 4, 5, 6, 7] 2 (1000, [0])
If we were instead looking for an unordered subsequence (subset but with multisets), we can use this O(N) time solution, which also returns the elements:
- If the array length is
L, return the array.
- If
L is odd and there are no positive values, use introselect to partition the array at index L, using (value) => -value as the key function for an ascending sort to get the maximum L values, and return them. This takes O(N) time.
- Use introselect to partition the array at index
L, using (value) => -abs(value) as the key function for an ascending sort. Let's call the first L items big and the rest small.
- Multiply the items in
big. If the result is not negative, return big.
- There are two possible swaps that will fix the sign, so check both and return the one with the bigger product:
- Swap the maximum value in
small (biggest positive) with the maximum negative value in big (smallest negative)
- Swap the minimum value in
small (biggest negative) with the minimum positive value in big (smallest positive)