[713][中等][二分][双指针] 乘积小于K的子数组
题目描述
给定一个正整数数组 nums。
找出该数组内乘积小于 k 的连续的子数组的个数。
示例 1:
输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。
说明:
0 < nums.length <= 50000
0 < nums[i] < 1000
0 <= k < 10^6
解题思路
前缀和 + 二分
可以参考[560][中等][前缀和][哈希] 和为K的子数组中的方法, 使用前缀和解题. 连乘经过的转换, 就转换成了连加. 而且由于题目中数组的长度和每个元素的大小情况, 很容易出现溢出的情况, 而转换也解决了这个问题.
先对原数组中的每个数字进行对数转换, 然后求得完整的前缀和数组, 然后求每个区间所有元素的连乘结果, 就是前缀数组对应位置之差, 这点与560题中一样. 不一样的是, 560题中只需要找到前缀和为差值为k
的前缀位置, 所以可以用哈希边遍历, 边存储, 边判断. 但本题是要找到所有小于k
的子数组, 看起来还是要对每个位置再遍历其开头(或结尾)的所有子数组, 一个个进行判别, 但前缀和数组的递增的特殊性质, 让我们可以用二分的方法进行优化.
对于每个位置, 假设我们要遍历以这个数字作为开头的子数组, 是否满足连乘小于, 我们可以对前缀和数组从的位置开始, 寻找值所处的位置, 小于这个数字的位置, 代表的从(包含)到这个位置组成的子数组, 其连乘结果肯定是小于k的, 因此如果我们用二分数组在前缀和数组上找到的位置为, 则以位置开头的子数组满足要求的数量为.
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
if k == 0:
return 0
k = math.log(k)
prefix = [0]
for num in nums:
prefix.append(prefix[-1] + math.log(num))
ans = 0
for i in range(len(nums)):
j = bisect.bisect(prefix, prefix[i] + k - 1e-9, lo=i + 1) # 减去1e-9是消除精度偏差带来的相同数字比较误差
ans += j - i - 1
return ans
双指针
对于每个, 我们要找到满足条件的最小的.满足 . 对于更靠右的, 其对应的最小的, 不可能小于靠左的对应的. 因此可以使用双指针, 移动右指针的同时, 不断调整左指针的位置.
我们使用一重循环枚举 ,同时设置 的初始值为 0。在循环的每一步中,表示 向右移动了一位,将乘积乘以 。此时我们需要向右移动 ,直到满足乘积小于 的条件。在每次移动时,需要将乘积除以 。当 移动完成后,对于当前的 ,就包含了 个乘积小于 的连续子数组。
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
if k <= 1:
return 0
left, right, mul = 0, 0, 1
ans = 0
while right < len(nums):
mul *= nums[right]
while mul >= k:
mul /= nums[left]
left += 1
ans += right - left + 1
right += 1
return ans
最后更新于
这有帮助吗?