713. Subarray Product Less Than K 乘积小于 K 的子数组
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/
@[toc]
题目地址: https://leetcode.com/problems/subarray-product-less-than-k/description/
题目描述
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.
题目大意
找出整数数组 nums
中,所有乘积小于 k
的「连续子数组」。
注意,「子数组」是连续的,而「子序列」可以不连续。
解题方法
滑动窗口
本题关键点:
- 是求「子数组」,是连续的;
- 数组中所有数字均为正数;
- 只求个数,不用求出所有的结果。
上面的这三条,正是使用「滑动窗口」来解决的完美条件。
可以使用我多次分享的滑动窗口模板解决,模板在代码之后。
本题的滑动窗口定义:所有元素的乘积严格小于 的连续子数组。
具体的做法:
- 使用 和 两个指针,分别指向滑动窗口的左右边界。
- 求窗口内所有的乘积,并判断:
- 主动右移: 指针每次移动一步:每次移动右指针之后,符合题目要求的结果增加的是此窗口内的子数组数量,也就是 ;
- 被动右移:如果乘积大于等于 了,需要移动左指针。
可以完全套用本文的滑动窗口模板,根本不用思考。
Python 代码如下:
class Solution(object):
def numSubarrayProductLessThanK(self, nums, k):
N = len(nums) # 数组/字符串长度
left, right = 0, 0 # 双指针,表示当前遍历的区间[left, right],闭区间
prods = 1 # 用于统计 子数组/子区间 是否有效,根据题目可能会改成求和/计数/乘积
res = 0 # 保存最大的满足题目要求的 子数组/子串 长度
while right < N: # 当右边的指针没有搜索到 数组/字符串 的结尾
prods *= nums[right] # 增加当前右边指针的数字/字符的求和/计数
while prods >= k and left <= right: # 此时需要一直移动左指针,直至找到一个符合题意的区间
prods /= nums[left] # 移动左指针前需要从counter中减少left位置字符的求和/计数
left += 1 # 真正的移动左指针,注意不能跟上面一行代码写反
# 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
res += right - left + 1 # 需要更新结果
right += 1 # 移动右指针,去探索新的区间
return res
C++ 代码如下:
class Solution {
public:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
const int N = nums.size();
int left = 0;
int right = 0;
long long prods = 1;
int res = 0;
while (right < N) {
prods *= nums[right];
while (prods >= k && left <= right) {
prods /= nums[left];
left ++;
}
res += right - left + 1;
right += 1;
}
return res;
}
};
Java 代码如下:
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
int N = nums.length;
int left = 0;
int right = 0;
long prods = 1;
int res = 0;
while (right < N) {
prods *= nums[right];
while (prods >= k && left <= right) {
prods /= nums[left];
left ++;
}
res += right - left + 1;
right += 1;
}
return res;
}
}
复杂度
- 时间复杂度:
- 空间复杂度:
分享滑动窗口模板
《挑战程序设计竞赛》这本书中把滑动窗口叫做「尺取法」,我觉得非常生动形象。因为滑动窗口的两个指针移动的过程和虫子爬动的过程非常像:前脚不动,把后脚移动过来;后脚不动,把前脚向前移动。
我分享一个滑动窗口的模板,能解决大多数的滑动窗口问题:
def findSubArray(nums):
N = len(nums) # 数组/字符串长度
left, right = 0, 0 # 双指针,表示当前遍历的区间[left, right],闭区间
sums = 0 # 用于统计 子数组/子区间 是否有效,根据题目可能会改成求和/计数/乘积
res = 0 # 保存最大的满足题目要求的 子数组/子串 长度
while right < N: # 当右边的指针没有搜索到 数组/字符串 的结尾
sums += nums[right] # 增加当前右边指针的数字/字符的求和/计数
while 区间[left, right]不符合题意:# 此时需要一直移动左指针,直至找到一个符合题意的区间
sums -= nums[left] # 移动左指针前需要从counter中减少left位置字符的求和/计数
left += 1 # 真正的移动左指针,注意不能跟上面一行代码写反
# 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
res = max(res, right - left + 1) # 需要更新结果
right += 1 # 移动右指针,去探索新的区间
return res
滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,而左指针在内部 while 循环中每次可能移动多步。右指针是主动前移,探索未知的新区域;左指针是被迫移动,负责寻找满足题意的区间。
模板的整体思想是:
- 定义两个指针
left
和right
分别指向区间的开头和结尾,注意是闭区间;定义sums
用来统计该区间内的各个字符出现次数; - 第一重
while
循环是为了判断right
指针的位置是否超出了数组边界;当right
每次到了新位置,需要增加right
指针的求和/计数; - 第二重
while
循环是让left
指针向右移动到[left, right]
区间符合题意的位置;当left
每次移动到了新位置,需要减少left
指针的求和/计数; - 在第二重
while
循环之后,成功找到了一个符合题意的[left, right]
区间,题目要求最大的区间长度,因此更新res
为max(res, 当前区间的长度)
。 right
指针每次向右移动一步,开始探索新的区间。
模板中的 sums
需要根据题目意思具体去修改,本题是求和题目因此把sums
定义成整数用于求和;如果是计数题目,就需要改成字典用于计数。当左右指针发生变化的时候,都需要更新 sums
。
另外一个需要根据题目去修改的是内层 while
循环的判断条件,即: 区间 不符合题意 。
对于本题而言,就是该区间内的元素的乘积 大于等于了 。
总结
- 滑动窗口可以直接套模板,用我的这个模板,很简单
日期
2018 年 10 月 14 日 —— 周赛做出来3个题,开心 2022 年 5 月 5 日 —— 开始居家办公