持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
题目链接:713. 乘积小于 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
整理题意
题目给定一个整数数组 nums
,返回连续子数组中累乘值小于 k
的个数。
解题思路分析
习惯性动作,首先观察题目数据范围:
- 给定的数组长度最大值为 ,暴力搜索所有连续子数组会
TLE
超时。 - 数组中元素大小在
[1, 1000]
以内,我们考虑前缀积的话会溢出int
,甚至会溢出long long
。
从数据范围得知我们无法通过暴力解决,前缀积的优化方法也行不通。
看到 连续区间,通常我们会想到 滑动窗口 ,维护滑动窗口中的累乘值严格小于 k
。滑动窗口的时间复杂度为 ,不仅优化了时间复杂度,同时还避免了溢出问题,因为累乘值 prod
不会超过 k * 1000
。
再考虑如何记录区间中的连续子数组个数:
- 枚举右区间,也就是每次增加一个右区间元素;
- 维护此时累乘值
prod
严格小于k
时所对应的左区间l
; - 对于当前增加的这个右区间元素,可以和我们所维护的区间中每一个元素形成连续子数组区间,包括自己。
- 所以增加的答案个数也就是当前区间中的元素个数
r - l + 1
具体实现
- 初始化左区间
l = 0
,答案ans = 0
,累乘值prod = 1
; - 从
[0, n - 1]
枚举右区间r
的值,也就是每次增加一个右区间元素,prod *= nums[r]
;(n
为数组nums
的长度,下标从0
开始) - 维护此时
prod
严格小于k
时所对应的左区间l
; - 记录答案
ans += r - l + 1;
复杂度分析
- 时间复杂度:,其中
n
是数组nums
的长度。左区间l
和右区间r
的增加次数都不超过n
。 - 空间复杂度:,仅需常数空间。
代码实现
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int n = nums.size();
//l为左区间,ans记录答案,prod记录乘积值
int l = 0, ans = 0, prod = 1;
//枚举右区间,也就是每次增加一个右区间元素
for(int r = 0; r < n; r++){
//记录区间乘积值
prod *= nums[r];
//维护prod,保证区间乘积值 prod 严格小于 k
while(l <= r && prod >= k){
prod /= nums[l];
l++;
}
//当前枚举的右区间元素,可以和区间中每一个元素形成子数组,包括自己
//所以子数组个数为 r - l + 1
ans += r - l + 1;
}
return ans;
}
};
总结
该题核心思路为 滑动窗口 ,利用滑动窗口维护累乘值和左区间,同时记录答案即可。难点在于如何统计区间中连续子数组的个数。这里需要通过枚举的思路,也就是每次增加一个右区间元素的方法来统计答案个数。
最后注意初始化边界以和一些细节上的处理即可。
结束语
自立,是对生命积极、自主、负责的态度。别拿着别人的地图找自己的路,也别做现成答案的乞讨者。要做生活的拓荒者,探寻智慧的宝藏,走出自己的人生路。