化繁为简:破解 C/C++ 713. 乘积小于 K 的子数组
2024-02-16 11:53:34
在 C/C++ 编程中,我们经常会遇到处理数组相关的问题。其中一个比较经典的问题就是寻找乘积小于 K 的连续子数组。这个问题考察了我们对数组遍历、滑动窗口等算法的理解和应用能力。
这个问题可以简单为:给定一个整数数组 nums 和一个正整数 k,找出数组中所有乘积小于 k 的连续子数组的个数。
例如,如果 nums = [10, 5, 2, 6],k = 100,那么乘积小于 100 的连续子数组有:[10], [5], [2], [6], [10, 5], [5, 2], [2, 6], [10, 5, 2],总共有 8 个。
解决这个问题,我们可以采用滑动窗口的思路。滑动窗口就像一个可以调整大小的窗户,在数组上移动。我们可以用两个指针 left 和 right 来表示窗口的左右边界。窗口内的元素就是我们当前正在考虑的子数组。
算法的步骤如下:
- 初始化 left 和 right 指针都为 0,表示窗口最初为空。
- 初始化一个变量 product,用来存储窗口内元素的乘积,初始值为 1。
- 初始化一个变量 count,用来存储满足条件的子数组个数,初始值为 0。
- 开始循环,right 指针不断向右移动,扩大窗口。
- 每次 right 指针移动后,将 nums[right] 乘到 product 中。
- 如果 product >= k,说明窗口内的乘积已经超过了限制,需要缩小窗口。
- 缩小窗口的方法是:将 left 指针向右移动,并将 nums[left] 从 product 中除掉。
- 重复步骤 6 和 7,直到 product < k。
- 此时,窗口内的子数组满足条件,将 right - left + 1 加到 count 中。
- 重复步骤 4 到 9,直到 right 指针到达数组末尾。
- 返回 count。
下面是用 C++ 代码实现的滑动窗口算法:
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
int n = nums.size();
int left = 0, right = 0;
int product = 1;
int count = 0;
while (right < n) {
product *= nums[right];
while (product >= k && left <= right) {
product /= nums[left];
left++;
}
count += right - left + 1;
right++;
}
return count;
}
这段代码中,count += right - left + 1;
这一行是理解滑动窗口算法的关键。当窗口内的乘积小于 k 时,以 right 指针指向的元素为结尾的子数组个数就是 right - left + 1。
例如,如果窗口内的元素是 [10, 5, 2],那么以 2 为结尾的子数组有:[2], [5, 2], [10, 5, 2],总共 3 个,正好是 right - left + 1。
常见问题解答
1. 为什么滑动窗口算法的时间复杂度是 O(n)?
答:在滑动窗口算法中,left 和 right 指针最多分别遍历数组一次,所以时间复杂度是 O(n)。
2. 为什么需要判断 left <= right?
答:当 product >= k 时,我们需要缩小窗口。如果 left > right,说明窗口已经为空,不需要再缩小了。
3. 为什么 count += right - left + 1?
答:当窗口内的乘积小于 k 时,以 right 指针指向的元素为结尾的子数组个数就是 right - left + 1。
4. 如果数组中包含负数,该算法还能用吗?
答:如果数组中包含负数,滑动窗口算法就不能直接使用了,因为负数会影响乘积的大小关系。我们可以考虑将负数单独处理,或者使用其他算法,比如前缀和加二分查找。
5. 如果数组中包含 0,该算法还能用吗?
答:如果数组中包含 0,滑动窗口算法仍然可以使用。当遇到 0 时,我们需要将 product 重置为 1,并将 left 指针移动到 0 的下一个位置。