Subarrays with Product Less than a Target (medium)
General Discussion
One approach can be to sort the array, and then traverse it while multiplying the elements from left till the end of the array, collecting the elements in a result variable whenever the product is less than the target. The problem is that the sorting step destroys the contiguity relationships among the array elements.
In this problem, we are looking for the consecutive elements forming a subarray whose product is less than the target. This is different from finding a subset of the array elements that meets the given criterion. By definition, subarray implies using a sequence of elements in the original array that are contiguous, whereas the order is irrelevant when forming subsets.
Dry-run templates
This is the implementation of the solution provided for the problem posed in the lesson:
from collections import dequedef find_subarrays(arr, target):result = []product = 1left = 0for right in range(len(arr)):product *= arr[right]while (product >= target and left < len(arr)):product /= arr[left]left += 1# since the product of all numbers from left to right is less than the target therefore,# all subarrays from left to right will have a product less than the target too; to avoid# duplicates, we will start with a subarray containing only arr[right] and then extend ittemp_list = deque()for i in range(right, left-1, -1):temp_list.appendleft(arr[i])result.append(list(temp_list))return resultdef main():print(find_subarrays([2, 5, 3, 10], 30))print(find_subarrays([8, 2, 6, 5], 50))main()
Students may be encouraged to run through the provided solution with the following sample inputs:
Sample Input #1
Input array: [2, 5, 3, 10]
Target: 30
Iteration No. | Line No. | Product | left | right | result |
- | 3-5 | 1 | 0 | - | [] |
1 | 7 | 2 | 0 | 0 | [] |
1 | 15-17 | 2 | 0 | 0 | [[2]] |
2 | 7 | 10 | 0 | 1 | [[2]] |
2 | 15-17 | 10 | 0 | 1 | [[2],[5]] |
2 | 15-17 | 10 | 0 | 1 | [[2],[5],[2,5]] |
3 | 7 | 30 | 0 | 2 | [[2],[5],[2,5]] |
3 | 8-10 | 15 | 1 | 2 | [[2],[5],[2,5]] |
3 | 15-17 | 15 | 1 | 2 | [[2],[5],[2,5],[3]] |
3 | 15-17 | 15 | 1 | 2 | [[2],[5],[2,5],[3],[5,3]] |
4 | 7 | 150 | 1 | 3 | [[2],[5],[2,5],[3],[5,3]] |
4 | 8-10 | 30 | 2 | 3 | [[2],[5],[2,5],[3],[5,3]] |
4 | 8-10 | 10 | 3 | 3 | [[2],[5],[2,5],[3],[5,3]] |
4 | 15-17 | 10 | 3 | 3 | [[2], [5], [2, 5], [3], [5, 3], [10]] |
Sample Input #2
Input array: [1, 10, 20, 2, 50]
Target: 500
Students may be encouraged to complete the dry-run with this input:
Iteration No. | Line No. | Product | left | right | result |
- | 3-5 | 1 | 0 | - | [] |
1 | 7 | 1 | 0 | 0 | [] |
1 | 15-17 | 1 | 0 | 0 | [[1]] |
2 | 7 | 10 | 0 | 1 | [[1]] |
2 | 15-17 | 10 | 0 | 1 | [[1],[10]] |
2 | 15-17 | 10 | 0 | 1 | [[1],[10],[1,10]] |
... | ... | ... | ... | ... | ... |
Step-by-step solution construction
Let’s iterate through the array from left to the right and generate the subarrays against each index.
Following is the explanation of the code below:
- On line 5, we initialize an empty array called
result
. - On line 6, we iterate through the input array,
arr
, from0
tolen(arr) -1
, that is, from the first to the last index of the array. - On lines 10-11, we generate subarrays from the
0
th index toright
, by appending the array items in reverse in a special data structure, a double-ended queue, also called a deque. The use of the deque is natural as we need to append the new elements to the left of the previous elements. A quick overview of the deque data structure in Python is provided here. - On line 12, we convert each generated subarray to a regular list and then append it to
result
.
from collections import dequedef find_subarrays(arr, target):print("Given array: "+str(arr))print("Given target: " + str(target))result = []for right in range(len(arr)):temp_list = deque()print("right: " + str(right))print("\tsub arrays computed in the current iteration are: ")for i in range(right, -1, -1):temp_list.appendleft(arr[i])result.append(list(temp_list))print("\t\ttemp_list",end=" ")print(list(temp_list))print("\tresult array at the end of iteration " + str(right) + " is " + str(result))return resultdef main():inputs = [[[2, 5, 3, 10], 30],[[8, 2, 6, 5], 50]]for i in range(len(inputs)):result = find_subarrays(inputs[i][0], inputs[i][1])print("\nThe subarrays for the array "+ str(inputs[i][0]) + " with target "+ str(inputs[i][1]) + " are: " + str(result))print("-"*100)#print(find_subarrays([8, 2, 6, 5], 50))main()
The above code generates the subarrays, but we haven’t put any condition to add only those subarrays to the result
whose product
is less than target
, so let’s add it now.
from collections import dequedef find_subarrays(arr, target):print("Given array: "+str(arr))print("Given target: " + str(target))result = []product = 1left = 0for right in range(len(arr)):print("right: " + str(right))product *= arr[right]print("\tproduct: "+str(product))while (product >= target and left < len(arr)):print("\tDividing product (" + str(product) + ") by arr[left] (" + str(arr[left]) + ")")product /= arr[left]print("\tupdated product: "+str(product))left += 1print("\tnew value for left index: "+ str(left))# since the product of all numbers from left to right is less than the target therefore,# all subarrays from left to right will have a product less than the target too; to avoid# duplicates, we will start with a subarray containing only arr[right] and then extend ittemp_list = deque()print("\tsub arrays computed in the current iteration are: ")for i in range(right, -1, -1):temp_list.appendleft(arr[i])result.append(list(temp_list))print("\t\ttemp_list: ",end=" ")print(list(temp_list))print("\tresult array at the end of iteration " + str(right) + " is " + str(result))return resultdef main():inputs = [[[2, 5, 3, 10], 30],[[8, 2, 6, 5], 50]]for i in range(len(inputs)):result = find_subarrays(inputs[i][0], inputs[i][1])print("\nThe subarrays for the array "+ str(inputs[i][0]) + " with target "+ str(inputs[i][1]) + " are: " + str(result))print("-"*100)#print(find_subarrays([8, 2, 6, 5], 50))main()
- On lines 6-7, we add two variables
product
&left
and initialize them to1
and0
respectively. - On line 10, we compute the
product
by multiplying it with the next item in the arrayarr[right]
. - On lines 12-16, we have added the checks to ensure that the elements in the subarray between
left
andright
yield aproduct
less thantarget
. As long asproduct
is greater than or equal totarget
andleft
has not reached the end of the array, we moveleft
one step forward to exclude the item pointed to byleft
. Also, we decrease theproduct
by dividing it by the excluded item.
As we can see from the output, although left
is being moved along correctly to filter out subarrays whose product exceeds target
, we are still collecting all the subarrays in result
. So, we need to modify the for
loop that generates and collects subarrays, to filter out the unwanted subarrays. The for
loop on lines 24-26 generates sub-arrays from right
to 0
, whereas the exact solution should incorporate the left
calculated in the while
loop on lines 12-16.
Find below the finalized code and compare its output with the output of the previous code:
from collections import dequedef find_subarrays(arr, target):print("Given array: "+str(arr))print("Given target: " + str(target))result = []product = 1left = 0for right in range(len(arr)):print("right: " + str(right))product *= arr[right]print("\tproduct: "+str(product))while (product >= target and left < len(arr)):print("\tDividing product (" + str(product) + ") by arr[left] (" + str(arr[left]) + ")")product /= arr[left]print("\tupdated product: "+str(product))left += 1print("\tnew value for left index: "+ str(left))# since the product of all numbers from left to right is less than the target therefore,# all subarrays from left to right will have a product less than the target too; to avoid# duplicates, we will start with a subarray containing only arr[right] and then extend ittemp_list = deque()print("\tsub arrays computed in the current iteration are: ")for i in range(right, left-1, -1):temp_list.appendleft(arr[i])result.append(list(temp_list))print("\t\ttemp_list: ",end=" ")print(list(temp_list))print("\tresult array at the end of iteration " + str(right) + " is " + str(result))return resultdef main():inputs = [[[2, 5, 3, 10], 30],[[8, 2, 6, 5], 50]]for i in range(len(inputs)):result = find_subarrays(inputs[i][0], inputs[i][1])print("\nThe subarrays for the array "+ str(inputs[i][0]) + " with target "+ str(inputs[i][1]) + " are: " + str(result))print("-"*100)#print(find_subarrays([8, 2, 6, 5], 50))main()