← 返回题目列表

乘积小于 K 的子数组怎么用滑动窗口统计?为什么一次能加 right-left+1 个?

中等 第 21 / 27 题 更新于 2026/07/31
滑动窗口子数组计数双指针

简化版

如果数组元素都是正数,子数组乘积会随着右端扩张而变大,随着左端收缩而变小,所以可以用滑动窗口。

维护窗口乘积 product,右指针加入新元素后,如果 product >= k 就不断移动左指针。

当窗口合法时,以 right 结尾的合法子数组数量是 right - left + 1

详细版

这题的关键条件是所有元素为正数。正数保证窗口右扩时乘积不会下降,左缩时乘积不会上升,因此满足滑动窗口单调性。

流程:

  1. right 从左到右遍历,把 nums[right] 乘进窗口;
  2. product >= k 时,持续除掉 nums[left] 并移动 left
  3. 调整后,窗口 [left, right] 内任意以 right 结尾的后缀子数组乘积都小于 k
  4. 所以答案加 right - left + 1

如果 k <= 1,由于元素为正整数,不可能有乘积小于 k 的非空子数组,直接返回 0

完整版教学

一、为什么这题能用滑动窗口

滑动窗口要求某种单调性:右端扩张会让条件更难满足,左端收缩会让条件更容易满足。本题中数组元素为正数,所以乘积乘上新元素不会变小,除掉左端元素不会变大。这个性质让我们可以用双指针维护一个最大合法窗口。

记忆钩子:正数乘积题能滑窗,混入 0 或负数就要重新分析单调性。

二、窗口维护的含义

窗口 [left, right] 表示当前乘积小于 k 的最长后缀窗口。每次右指针加入一个数后,乘积可能超标,于是移动左指针直到重新合法。调整完成后,left 是让窗口合法的最靠左位置。

while (product >= k) {
  product /= nums[left]
  left++
}

这个循环不是随便缩,而是在恢复不变量:当前窗口乘积必须小于 k

三、为什么答案加 right-left+1

调整完成后,窗口 [left, right] 的乘积小于 k。由于所有元素为正数,窗口内部任意以 right 结尾的后缀子数组,乘积只会更小或相等。例如合法窗口是 [2,5,3],以右端 3 结尾的子数组有 [3][5,3][2,5,3],一共 3 个。

窗口长度以 right 结尾的合法子数组数
11
22
33

所以每轮贡献是 right - left + 1

四、带数字推演

数组 [10,5,2,6]k = 100

right=0: product=10,合法,+1 -> [10]
right=1: product=50,合法,+2 -> [5], [10,5]
right=2: product=100,不合法,除 10 后 product=10,+2 -> [2], [5,2]
right=3: product=60,合法,+3 -> [6], [2,6], [5,2,6]

总数是 8

五、代码模板

实现如下:

function numSubarrayProductLessThanK(nums, k) {
  if (k <= 1) return 0
  let left = 0
  let product = 1
  let ans = 0
  for (let right = 0; right < nums.length; right++) {
    product *= nums[right]
    while (product >= k) {
      product /= nums[left]
      left++
    }
    ans += right - left + 1
  }
  return ans
}

这里统计的是连续子数组,不是子序列。滑动窗口只适合连续区间。

六、常见误区与追问

  • 误区:忘记处理 k <= 1。 正整数乘积至少为 1,不可能小于等于 1 的阈值。
  • 误区:窗口合法后只加 1。 以 right 结尾的所有后缀都合法,应该加窗口长度。
  • 误区:数组有负数也直接套模板。 负数会破坏乘积单调性,窗口移动不再可靠。
  • 追问:为什么不是统计所有窗口长度组合? 每个子数组按右端点唯一归类,每轮只统计以当前 right 结尾的子数组,避免重复。
  • 追问:复杂度为什么是 O(n)? left 和 right 都只单调右移,每个元素最多进出窗口一次。

这些问题的核心都是“正数带来的乘积单调性”。

七、加强记忆

这题记成“正数乘积,超标左缩,合法加长度”。右端加入新数让乘积变大,超过 k 就移动左端恢复合法;恢复后,窗口内所有以 right 结尾的后缀都合法,所以一次加 right-left+1。只要看到正数、连续子数组、乘积或和的阈值统计,就可以先想滑动窗口。