Your are given an array of positive integers nums
.
Count and print the number of (contiguous) subarrays where the product of all the elements in the subarray is less than k
.
Example 1:
Input: nums = [10, 5, 2, 6], k = 100 Output: 8 Explanation: The 8 subarrays that have product less than 100 are: [10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [5, 2, 6]. Note that [10, 5, 2] is not included as the product of 100 is not strictly less than k.
Note:
0 < nums.length <= 50000
.0 < nums[i] < 1000
.0 <= k < 10^6
.
Idea 1. Brute force, any subarray nums[i..j] can be represented by a given pair of integers 0 <= i <= j < nums.length.
Time complexity: O(n2)
Space complexity: O(1)
1 class Solution { 2 public int numSubarrayProductLessThanK(int[] nums, int k) { 3 int count = 0; 4 for(int i = 0; i < nums.length; ++i) { 5 int product = 1; 6 for(int j = i; j < nums.length; ++j) { 7 product *= nums[j]; 8 if(product < k) { 9 ++count; 10 } 11 else break; 12 } 13 } 14 return count; 15 } 16 }
Idea 2. Binary search. Taking advantage:
1. log(x*y) = log(x) + log(y)
2. positive numbers mean monoto increasing for subarray product(or log subarray sum), the prefix product/sum is sorted
reducing subarry product problem to subarray sum problem. For each index i, find the rightmost index j such that prefix[j] - nums[i-1] < Math.log(k). Note the comparison for double, reduce log(k) by 1e-9, to avoid searching the wrong half.
Take nums= {10,3,3,7,2,9,7,4,7,2,8,6,5,1,5}, k = 30 for example,
when searching the subarray {6, 5, 1, 5}, 6* 5 * 1 == 30, if not adding 1e-9, the search will move to {5} because of doble comparison, hence make logK slightly smaller, if the product is equal to k, move to the lower half.
Time complexity: O(nlogn)
Space complexity: O(n)
1 class Solution { 2 int findMaxIndex(double[] prefix, int left, int right, int k) { 3 double logK = Math.log(k); 4 int i = left, j = right; 5 while(i < j) { 6 int mid = i + (j - i) / 2; 7 if(prefix[mid] - prefix[left-1] < logK - 1e-9) i = mid + 1; 8 else j = mid; 9 } 10 return i; 11 } 12 13 public int numSubarrayProductLessThanK(int[] nums, int k) { 14 int count = 0; 15 16 double[] prefix = new double[nums.length + 1]; 17 for(int i = 1; i < prefix.length; ++i) { 18 prefix[i] = prefix[i-1] + Math.log(nums[i-1]); 19 } 20 21 for(int i = 1; i < prefix.length; ++i) { 22 int maxIndex = findMaxIndex(prefix, i, prefix.length, k); 23 count += maxIndex - i; 24 } 25 return count; 26 } 27 }
Idea 3. Slicing widnow. Keeping a max-product-windown less than k. Since the elements in the array is positive, the subarray product is a monotone increasing fuction, so we use a sliding window.
Time complexity: O(n)
Space complexity: O(1)
3.a Fixed the left point of a subarray, for each left, find the largest right so that the product of the subarray nums[left] + nums[left+1] + ... + nums[right-1] is less than k. Hence the product of each subarray starting at left and ending at rightEnd (left <= rightEnd < right) is less than k, there are right - left such subarrays. For every left, we update right and prod to maintain the invariant.
Starting with index left ( left = 0), find the first largest right so that the product * nums[right] is larger than k, add right - left to the count.
Shifting the window by moving left to the right by element, update product (product = product/nums[left]), the new subarry will start with index left = left + 1,
if the product*nums[right] is still larger than k, there are right - left subarrys with product less than k; Note: if right == left, it's empty array, no need to update product, product should remain as 1, it means nums[right] > k, we need to move right forward to maintain the invariant left <= right.
otherwise, expand right until product * nums[right] > k, continue the above process until left reaches the end of the array.
Take nums = [10, 5, 2, 100, 6], k = 100 for example,
left = 0, right = 2, product = 100, [10, 5, 2] , there are 2 (right - left) subarrays starting at 0 with product less than 100: [10], [10, 5]
left moving forward, left = 1, product = 100/nums[0] = 10, continue expand right until product = 1000 >= k, right = 3, [5, 2, 100], 2 (right - left) subarrays starting with 5: [5], [5, 2]
left moving forward, left = 2, product = 1000/nums[1] = 200, since product is already larger than k, no need to expand right, keep right= 3, 1 (right - left) subarray starting with 2: [2]
left moving forward, left = 3, right = 3, since left < right, as we define right as the excluded boundary, there is no subarry in the range (right - left) = 0, right is not in the product, we need to move right, right = 4
left moving forward, left = 4, product = 1, 1 * 6 < 100, expanding right = 5, 1 subarray starting with 6, [6]
(10)
(10, 5, 2)
(5, 2, 100)
(2, 100)
(100)
(6)
1 class Solution { 2 public int numSubarrayProductLessThanK(int[] nums, int k) { 3 int count = 0; 4 int product = 1; 5 for(int left = 0, right = 0; left < nums.length; ++left) { 6 for(;right < nums.length && product * nums[right] < k; ++right) { 7 product *= nums[right]; 8 } 9 10 count += right - left; 11 if(left < right) { 12 product = product/nums[left]; 13 } 14 else { 15 ++right; 16 } 17 } 18 return count; 19 } 20 }
3.b Fix the right point, for each right point, find the smallest left such that the product is less than k.
Shifting the window by adding a new element on the right,
if the product is less than k, then all the subarray nums[left...right] satisfy the requrement, there are right - left + 1 such subarrays ending at index right;
otherwise, shrinking the subarray by moving left index to the right until the subarray product less than k; Note if nums[right] >= k, left will move one step before right, the arry ending at right is empty with right - left + 1 = 0.
(10)
(10, 5)
(5, 2)
(100) right = left + 1, count = 0
(6)
1 class Solution { 2 public int numSubarrayProductLessThanK(int[] nums, int k) { 3 int count = 0; 4 int product = 1; 5 for(int left = 0, right = 0; right < nums.length; ++right) { 6 product *= nums[right]; 7 while(left <= right && product >= k) { 8 product /= nums[left]; 9 ++left; 10 } 11 count += right - left + 1; 12 } 13 return count; 14 } 15 }