【C/C++】713. 乘积小于 K 的子数组

280 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情


题目链接:713. 乘积小于 K 的子数组

题目描述

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

提示:

  • 1nums.length31041 \leqslant nums.length \leqslant 3 * 10^4
  • 1nums[i]10001 \leqslant nums[i] \leqslant 1000
  • 0k1060 \leqslant k \leqslant 10^6

示例 1:

输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[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

整理题意

题目给定一个整数数组 nums ,返回连续子数组中累乘值小于 k 的个数。

解题思路分析

习惯性动作,首先观察题目数据范围:

  • 给定的数组长度最大值为 31043 * 10^4 ,暴力搜索所有连续子数组会 TLE 超时。
  • 数组中元素大小在 [1, 1000] 以内,我们考虑前缀积的话会溢出 int ,甚至会溢出 long long

从数据范围得知我们无法通过暴力解决,前缀积的优化方法也行不通。

看到 连续区间,通常我们会想到 滑动窗口 ,维护滑动窗口中的累乘值严格小于 k。滑动窗口的时间复杂度为 O(n)O(n) ,不仅优化了时间复杂度,同时还避免了溢出问题,因为累乘值 prod 不会超过 k * 1000

再考虑如何记录区间中的连续子数组个数:

  1. 枚举右区间,也就是每次增加一个右区间元素;
  2. 维护此时累乘值 prod 严格小于 k 时所对应的左区间 l
  3. 对于当前增加的这个右区间元素,可以和我们所维护的区间中每一个元素形成连续子数组区间,包括自己。
  4. 所以增加的答案个数也就是当前区间中的元素个数 r - l + 1 乘积小于k.jpg

具体实现

  1. 初始化左区间 l = 0 ,答案 ans = 0 ,累乘值 prod = 1
  2. [0, n - 1] 枚举右区间 r 的值,也就是每次增加一个右区间元素,prod *= nums[r];( n 为数组 nums 的长度,下标从 0 开始)
  3. 维护此时 prod 严格小于 k 时所对应的左区间 l
  4. 记录答案 ans += r - l + 1;

复杂度分析

  • 时间复杂度:O(n)O(n),其中 n 是数组 nums 的长度。左区间 l 和右区间 r 的增加次数都不超过 n
  • 空间复杂度:O(1)O(1),仅需常数空间。

代码实现

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        int n = nums.size();
        //l为左区间,ans记录答案,prod记录乘积值
        int l = 0, ans = 0, prod = 1;
        //枚举右区间,也就是每次增加一个右区间元素
        for(int r = 0; r < n; r++){
            //记录区间乘积值
            prod *= nums[r];
            //维护prod,保证区间乘积值 prod 严格小于 k
            while(l <= r && prod >= k){
                prod /= nums[l];
                l++;
            }
            //当前枚举的右区间元素,可以和区间中每一个元素形成子数组,包括自己
            //所以子数组个数为 r - l + 1
            ans += r - l + 1;
        }
        return ans;
    }
};

总结

该题核心思路为 滑动窗口 ,利用滑动窗口维护累乘值和左区间,同时记录答案即可。难点在于如何统计区间中连续子数组的个数。这里需要通过枚举的思路,也就是每次增加一个右区间元素的方法来统计答案个数。

最后注意初始化边界以和一些细节上的处理即可。


结束语

自立,是对生命积极、自主、负责的态度。别拿着别人的地图找自己的路,也别做现成答案的乞讨者。要做生活的拓荒者,探寻智慧的宝藏,走出自己的人生路。