返回

化繁为简:破解 C/C++ 713. 乘积小于 K 的子数组

后端

在 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 来表示窗口的左右边界。窗口内的元素就是我们当前正在考虑的子数组。

算法的步骤如下:

  1. 初始化 left 和 right 指针都为 0,表示窗口最初为空。
  2. 初始化一个变量 product,用来存储窗口内元素的乘积,初始值为 1。
  3. 初始化一个变量 count,用来存储满足条件的子数组个数,初始值为 0。
  4. 开始循环,right 指针不断向右移动,扩大窗口。
  5. 每次 right 指针移动后,将 nums[right] 乘到 product 中。
  6. 如果 product >= k,说明窗口内的乘积已经超过了限制,需要缩小窗口。
  7. 缩小窗口的方法是:将 left 指针向右移动,并将 nums[left] 从 product 中除掉。
  8. 重复步骤 6 和 7,直到 product < k。
  9. 此时,窗口内的子数组满足条件,将 right - left + 1 加到 count 中。
  10. 重复步骤 4 到 9,直到 right 指针到达数组末尾。
  11. 返回 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 的下一个位置。