刷题日记乘积小于K的子数组
Posted 傅耳耳
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题日记乘积小于K的子数组相关的知识,希望对你有一定的参考价值。
乘积小于K的子数组
给定一个正整数数组 nums
和整数 k ,请找出该数组内乘积小于 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的子数组。
示例 2:
输入: nums = [1,2,3], k = 0
输出: 0
提示:
1 <= nums.length <= 3 * 10^4
1 <= nums[i] <= 1000
0 <= k <= 10^6
思路一:前缀和 + 二分
二分查找常用函数:
头文件 #include <algorithm>
upper_bound(begin, end, num)
:返回在[begin, end)
二分查找的**第一个大于num
** 的数的索引,不存在返回end
lower_bound(begin, end, num)
:返回在[begin, end)
二分查找的**第一个大于等于num
** 的数的索引,不存在返回end
算法思路:
-
使用乘积作为二分的依据,会出现 整形溢出,防止溢出,等式两边取对数:
-
mul[i+1] = mul[i] * nums[i]; //乘积会整型溢出 // 等式两边取对数 log(mul[i+1]) = log(mul[i] * nums[i]) = log(mul[i]) + log(nums[i]); // 乘积变为对数和 // 令 sum[i] = log(mul[i]) sum[i+1] = sum[i] + log(nums[i]);
-
-
题目所求为 乘积小于k的子数组,遍历每个数字
nums[i]
,枚举区间左端点i
,找最小边界bound
,满足:i <= bound
nums[i]*num[i+1]*...*nums[bound] >= k
即sum[bound] - sum[i-1] >= log(k)
- 找 第一个
bound
,sum[bound] >= log(k) + sum[i-1]
==> 使用lower_bound
-
注意:因为涉及到对数,因此
sum[]
数组使用double
类型double
类型只保证15位小数精确,为防止计算误差:-
题目中
1 <= nums[i] <= 1000
且1 <= nums.length <= 3 * 10^4
,可得最大乘积为1000^3*10^4
,取对数为30000*ln(1000)
,ln(1000)=6.9
,整数数位最大为6位 -
防止计算误差(即15位后省去的小数),小数最少为9位,加上
1e-10
sum[bound] - sum[i-1] + 1e-10 >= log(k)
条件:
sum[bound] >= log(k) + sum[i -1] - 1e-10
-
-
对每个区间左端点
i
,找到的边界bound
,则以下区间均满足条件:[i, i], [i, i+1], [i, i+2],..., [i, bound-1]
共
bound - 1 - i + 1 = bound - i
个区间
代码:
class Solution
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k)
int n = nums.size();
// long long mul[n+1];
double sum[n+1];
for(int i = 0;i < n;i ++ )
// mul[i+1] = mul[i] * nums[i]; //乘积会整型溢出
// 等式两边取对数 log(mul[i+1]) = log(mul[i] * nums[i]) = log(mul[i]) + log(nums[i])
// 令 sum[i] = log(mul[i])
sum[i+1] = sum[i] + log(nums[i]);
int cnt = 0;
for(int i = 1;i <= n;i ++ )
int l = lower_bound(sum + i, sum + n + 1, sum[i-1] + log(k) - 1e-10) - sum;
cnt += (l - i);
return cnt;
;
// 遍历每个i 二分找第一个乘积大于等于k的边界bound 则i...bound-1都满足条件
时间复杂度:O(nlogn)
空间复杂度:O(n)
思路二:滑动窗口
双指针 left, right
算法思路:
- 窗口右边界逐个递增(迭代)
- 当
[left, right]
内乘积prod >= k
时,窗口左边界右移,减小窗口,直到prod < k
为止 - 过程中不断叠加统计
[left, right]
内的新增的子区间数量
【统计子区间】
例:10 5 2 6 k = 101
10 5 2 6
区间1:l,r [10] +1
区间2:l r [10][5][10,5] +1+2
区间3:l r [10][5][10,5][2][5,2][10,5,2] +1+2+3
区间4:l r 10*5*2*6=600 > 101
区间5: l r [6][2,6][5,2,6] +1+2+3+3
l r 结束
注:
1.对每个区间[l,r]只统计包括nums[r]的子区间 ===> 防止重复
2.对每个满足乘积prod < k的区间统计子区间个数
3.[规律]每个区间增加的子区间个数为 <窗口长度>
代码:
class Solution
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k)
if(k == 0) return 0;
int n = nums.size();
int l = 0, r = 0;
int prod = 1;
int res = 0;
while(r < n)
prod *= nums[r];
while(l <= r && prod >= k)
prod /= nums[l];
l ++ ;
// 满足 prod < k 统计子区间个数
if(l <= r)
res += (r - l + 1);
// 加窗口长度
r ++ ;
return res;
;
时间复杂度:O(n)
空间复杂度:O(1)
以上是关于刷题日记乘积小于K的子数组的主要内容,如果未能解决你的问题,请参考以下文章