文章

2302. 统计得分小于 K 的子数组数目(Rating 1808)

2302. 统计得分小于 K 的子数组数目(Rating 1808)

以下内容偏向于记录个人练习过程及思考,非常规题解内容

题目

2302. 统计得分小于 K 的子数组数目

Rating 1808

思路

看到题目数据范围是 1e5,猜测大概率是个 O(n) 或者 O(nlogn) 的做法。

考虑固定子数组的右端点时,如何寻找左端点?

注意到子数组单调性,左端点越靠左,那么得分越高;反之则越低。

因此可以考虑二分:

遍历数组,将第 i 个位置当作右端点,然后利用单调性二分查找 [0, i] 之间的数作为子数组左端点,找到子数组得分严格小于 k 且左端点尽量靠左的位置即可。

为了每次二分时,快速计算出得分,这里需要提前维护一个前缀和,可以快速得到区间内的数字之和。

提交后虽然通过了,但是发现耗时比较高。

看了下之前提交通过的代码,发现之前自己用滑动窗口也通过了此题。

为啥本题可以用滑动窗口?

参考灵神的题解

  • 连续子数组
  • 有单调性

因此,以后对于满足以上2个条件的题目,可以先优先考虑一下滑动窗口是否可以解决,算法复杂度更低。

代码

二分法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        n = len(nums)
        prs = [0] * (n + 1) # 维护前缀和
        for i, num in enumerate(nums):
            prs[i + 1] = prs[i] + num
        ret = 0
        for i in range(n):
            lb, rb = 0, i # 二分[0, i]
            xb = -1 # 待找的左边界
            while lb <= rb:
                mid = (lb + rb) // 2
                s = (prs[i + 1] - prs[mid]) * (i - mid + 1)
                if s < k:
                    xb = mid
                    rb = mid - 1
                else:
                    lb = mid + 1
            if xb < 0: # 如果待找的左边界仍为默认值-1,说明以i为右端点的所有子数组都不满足严格,未找到
                continue
            ret += (i - xb + 1) # 将[xb, i]中间的子数组数量加入答案
        return ret    

滑动窗口

1
2
3
4
5
6
7
8
9
10
class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        n, l, s, ret = len(nums), 0, 0, 0
        for r, num in enumerate(nums): # 遍历右端点,移动左端点
            s += num # 更新区间和
            while s * (r - l + 1) >= k: # 只要>=k就移动左端点
                s -= nums[l] # 更新区间和
                l += 1 # 移动左端点
            ret += r - l + 1 # 更新答案
        return ret
本文由作者按照 CC BY 4.0 进行授权