大小为 K 且平均值大于等于阈值的子数组数目

标签: 数组 滑动窗口

难度: Medium

给你一个整数数组 arr 和两个整数 k 和 threshold 。

请你返回长度为 k 且平均值大于等于 threshold 的子数组数目。

示例 1:

输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
输出:3
解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。

示例 2:

输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5
输出:6
解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。

提示:

  • 1 <= arr.length <= 105
  • 1 <= arr[i] <= 104
  • 1 <= k <= arr.length
  • 0 <= threshold <= 104

Submission

运行时间: 60 ms

内存: 26.1 MB

from typing import List

class Solution:
    def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
        count = 0  # 用于计数符合条件的子数组数目
        sum_k = sum(arr[:k])  # 初始化和为前 k 个元素的和
        
        # 判断第一个长度为 k 的子数组的平均值是否大于等于 threshold
        if sum_k / k >= threshold:
            count += 1
        
        # 从第 k+1 个元素开始,使用滑动窗口更新和
        for i in range(k, len(arr)):
            sum_k = sum_k - arr[i-k] + arr[i]  # 滑动窗口更新和
            if sum_k / k >= threshold:
                count += 1
        
        return count

from typing import List

class Solution:
    def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
        count = 0  # 用于计数符合条件的子数组数目
        sum_k = sum(arr[:k])  # 初始化和为前 k 个元素的和
        threshold_k = threshold * k  # 将 threshold 乘以 k 以避免浮点除法
        
        # 判断第一个长度为 k 的子数组的平均值是否大于等于 threshold
        if sum_k >= threshold_k:
            count += 1
        
        # 从第 k+1 个元素开始,使用滑动窗口更新和
        for i in range(k, len(arr)):
            sum_k = sum_k - arr[i-k] + arr[i]  # 滑动窗口更新和
            if sum_k >= threshold_k:
                count += 1
        
        return count

Explain

本题解采用滑动窗口的方法来解决问题。首先计算数组中前k个元素的和,然后根据这个和来判断第一个长度为k的子数组的平均值是否大于等于给定的阈值。为避免在每次比较时进行浮点数除法,我们将阈值乘以k,这样只需要比较整数即可。接下来,通过滑动窗口逐步向右移动,即每次从当前和中减去最左边的元素值,加上新的右边的元素值,然后更新和,再判断当前和是否满足条件。这种方法避免了对每个子数组重新计算和的需要,提高了效率。

时间复杂度: O(n)

空间复杂度: O(1)

from typing import List

class Solution:
    def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
        count = 0  # 用于计数符合条件的子数组数目
        sum_k = sum(arr[:k])  # 初始化和为前 k 个元素的和
        threshold_k = threshold * k  # 将 threshold 乘以 k 以避免浮点除法
        
        # 判断第一个长度为 k 的子数组的平均值是否大于等于 threshold
        if sum_k >= threshold_k:
            count += 1
        
        # 从第 k+1 个元素开始,使用滑动窗口更新和
        for i in range(k, len(arr)):
            sum_k = sum_k - arr[i-k] + arr[i]  # 滑动窗口更新和
            if sum_k >= threshold_k:
                count += 1
        
        return count

Explore

滑动窗口方法特别适合用于处理固定长度的子数组问题,因为它可以有效利用前一个窗口的计算结果来更新当前窗口的值,从而避免重复计算每个子数组的和。这样做可以将时间复杂度降低到O(n)。对于本题,动态规划不是最优选择,因为它通常用于处理涉及最优子结构和状态转移的问题,而本题的主要任务是计算和并进行比较,没有涉及到复杂的状态转移或者多阶段决策,因此使用滑动窗口更为直接和高效。

如果数组长度小于k,那么不可能形成一个长度为k的子数组。在这种情况下,根据题目的要求和逻辑,我们应该直接返回0,因为没有任何子数组符合长度要求。在实际编程实现中,可以在函数开始处添加一个检查,如果数组长度小于k,则直接返回0。

将阈值乘以k并比较整数的方法避免了浮点数计算。浮点数计算可能涉及精度问题,并且在某些情况下,浮点数运算是比整数运算更慢的。通过使用整数比较,我们可以确保比较的精确性并可能提高代码的执行效率。此外,这种方法在逻辑上更简单直观,易于理解和维护。

算法实现考虑了数组中可能存在负数的情况。在这种算法中,不论数组元素是正数还是负数,算法都只关心子数组的总和是否达到阈值的k倍。这意味着只要子数组的平均值达到或超过给定的阈值,无论其中包含何种元素(正数或负数),都会被计入符合条件的子数组数目。因此,数组中元素的正负并不直接影响计数逻辑,只影响和的计算结果。