在问 · 算法题解

713. 乘积小于 K 的子数组

题目描述

输入一个正整数数组 nums 和一个整数 k。

统计有多少个连续非空子数组,其所有元素的乘积严格小于 k,返回这个数量。起止位置不同的子数组分别计数。

示例 1
输入:nums = [10,5,2,6], k = 100
输出:8

解释:满足条件的有 [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

解释:正整数的乘积不可能小于 0。

数据范围

  • 1 <= nums.length <= 3 * 10^4
  • 1 <= nums[i] <= 1000
  • 0 <= k <= 10^6

解题思路:滑动窗口

所有数都是正整数,窗口扩大时乘积只增不减。用滑动窗口:右端 right 每次右移并乘进 nums[right];乘积 ≥ k 就不断除掉左端、左端右移。窗口稳定后,以 right 结尾的合法子数组正好是 [left..right]、[left+1..right]……[right..right],共 right - left + 1 个。

为什么正确

每轮结束时,窗口 [left, right] 是以 right 结尾、乘积 < k 的最长子数组。

复杂度分析

时间 O(n),每个元素最多进出窗口一次;空间 O(1)。

易错点

k ≤ 1 时没有任何正整数乘积小于 k,必须提前返回 0;否则 while 循环会把 left 推过 right,甚至越界。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        if k <= 1:
            return 0
        prod = 1
        ans = left = 0
        for right, x in enumerate(nums):
            prod *= x
            while prod >= k:
                prod //= nums[left]
                left += 1
            ans += right - left + 1
        return ans

载入编辑器,自己跑一遍 →

其他解法

两种解法都利用“元素为正,左端点右移乘积不增”的单调性;二分查找对每个右端点重新找左端点,滑动窗口让左端点只向右移动,均摊 O(1)。

解法 2:对数前缀和 + 二分查找 时间 O(n log n),空间 O(n)

取对数把乘积变成和:乘积小于 k 等价于对数和小于 log k。对 log(nums[i]) 求前缀和 logs,它严格单调不减。对每个右端点 j,用二分找到最小的 l,使 logs[j + 1] − logs[l] < log k,以 j 结尾的合法子数组有 j + 1 − l 个。比较时加一个 10⁻¹⁰ 的容差,避免乘积恰好等于 k 时浮点误差导致误判。

取对数后不会溢出,并把问题转成熟悉的前缀和上二分。代价是多一个 log 因子,还要处理浮点误差。

class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        if k <= 1:
            return 0
        n = len(nums)
        logs = [0.0] * (n + 1)
        for i, x in enumerate(nums):
            logs[i + 1] = logs[i] + math.log(x)
        logk = math.log(k)
        ans = 0
        for j in range(n):
            lo, hi = 0, j + 1
            while lo < hi:
                mid = (lo + hi) // 2
                if logs[j + 1] - logs[mid] < logk - 1e-10:
                    hi = mid
                else:
                    lo = mid + 1
            ans += j + 1 - lo
        return ans

二分查找 专题的其他题目

查看 二分查找 全部题目 →