leetcode 713. 乘积小于K的子数组(滑动窗口或二分)

给定一个正整数数组 nums。

找出该数组内乘积小于 k 的连续的子数组的个数。

示例 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的子数组。

需要注意到数组内每一个数字都是正数,所以随着子数组的中数字个数增多,乘积也是单调递增的,这是这个题的关键。

找到这个单调递增的关系之后,有两种办法解决这个问题。

方法一:

首先是二分+前缀和,前缀和如不知请自行点击,利用前缀和可以在O(1)时间内求得一个子数组的乘积,那么对于数组的每一位i,我们可以从后向前找到最长的以i位为结尾子数组使得该子数组的乘积小于k。

注意到随着数组变长乘积单增,那么用一下二分枚举中点加上前缀和就能解决问题。

不过还有一点,这里前缀和保存的是乘积,容易爆掉long int的范围,为了避免这一点,可以取对数将乘积变成加法运算。

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        if (k == 0) return 0;
        double logk = Math.log(k);
        double[] prefix = new double[nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
            prefix[i+1] = prefix[i] + Math.log(nums[i]);
        }

        int ans = 0;
        for (int i = 0; i < prefix.length; i++) {
            int lo = i + 1, hi = prefix.length;
            while (lo < hi) {
                int mi = lo + (hi - lo) / 2;
                if (prefix[mi] < prefix[i] + logk - 1e-9) lo = mi + 1;
                else hi = mi;
            }
            ans += lo - i - 1;
        }
        return ans;
    }
}


代码原地址

方法二:

注意到上文说的单增的性质,假如[L,R]区间的子数组乘积>=k,那么无论如何扩大R,得到的子数组都不可能满足题意要得到满足题意的子数组只能扩大L,想到这点很关键。

那么我们要维护一个last[i]数组,last[i]代表从第i位开始满足题意的子数组最远可以到达哪一位。

从L=1开始,让R不断扩大,直到子数组乘积>=k,那么就得到了last[1],然后让L++,再去判断子数组乘积决定是否要移动R,注意R不可能缩小,以此类推就能得到所有的last[i]。

得到last数组之后,累加每一个(last[i]-i)就是答案,这个也很显然,画画图就明白。

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        int len = nums.length;
        int sum = 1;
        int last = -1;
        int ans = 0;
        for(int i=0;i<len;i++)
        {
            if(i>0)
                sum = sum / nums[i-1];
            while(sum<k&&last<len)
            {
                last++;
                if(last>=len)
                    break;
                sum = sum * nums[last];
            }
            ans = ans + Math.max(last-i,0);
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值