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

其实我先做了这一题,很简单:

643. 子数组最大平均数 I - 力扣(LeetCode)

1 题目

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

给你一个整数数组 arr 和两个整数 kthreshold

请你返回长度为 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

2 代码实现

cpp 复制代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        int currentSum = 0 ;
        int count = 0 ;

        for (int i = 0 ; i < k ; i ++ ){
            currentSum += arr[i];
        }

         if( (currentSum / k) >= threshold ){
                count++;
            }

        for (int i = k ; i < n ; i++){
            currentSum = currentSum - arr[i - k] + arr [i];
            if( (currentSum / k) >= threshold ){
                count++;
            }
        }
        return count;
    }
};

错误的代码:

cpp 复制代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        int currentSum = 0 ;
        int count = 0 ;

        for (int i = 0 ; i < k ; i ++ ){
            currentSum += arr[i];
            if( (currentSum / k) >= threshold ){
                count++;
            }
        }
        for (int i = k ; i < n ; i++){
            currentSum = currentSum - arr[i - k] + arr [i];
             if( (currentSum / k) >= threshold ){
                count++;
            }
        }

        return count;
    }
};

纠正:

你的代码错误核心是 第一个循环的逻辑判断时机不对,导致在计算 "第一个完整窗口" 的和之前,就提前做了平均值判断 ------ 相当于统计了 "长度小于 k 的子数组",和题目要求的 "长度必须为 k 的连续子数组" 不符。

一、先看错误出在哪:第一个循环的问题

题目要求子数组 长度必须是 k ,但你的第一个循环(for (int i = 0; i < k; i++))里,每累加一个元素就判断一次 currentSum / k >= threshold,这是错误的:

  • i=0 时:只累加了 1 个元素(长度 = 1),就用 currentSum/3(假设 k=3)判断,显然不符合 "长度为 k" 的要求;
  • i=1 时:累加了 2 个元素(长度 = 2),还是用 currentSum/3 判断,依然不符合;
  • 只有当 i=k-1 时(循环最后一次),才累加完 k 个元素(第一个完整窗口),此时判断才是正确的。

简单说:第一个循环里,你多判断了 k-1 次 "无效子数组"(长度不足 k),导致计数偏多,最终答案错误。

二、举个例子直观感受错误

比如用示例 1 测试你的代码:arr = [2,2,2,2,5,5,5,8], k=3, threshold=4第一个循环(i 从 0 到 2):

  • i=0:currentSum=2 → 2/3=0 ≥4?否(count=0);
  • i=1:currentSum=4 →4/3≈1 ≥4?否(count=0);
  • i=2:currentSum=6 →6/3=2 ≥4?否(count=0);这一步还好,没多计数。但换个测试用例就会出错:比如 arr=[5,5,5], k=3, threshold=5
  • i=0:currentSum=5 →5/3≈1 ≥5?否;
  • i=1:currentSum=10 →10/3≈3 ≥5?否;
  • i=2:currentSum=15 →15/3=5 ≥5?是(count=1);这一步结果正确,但如果是 arr=[6,6,6], k=3, threshold=2
  • i=0:currentSum=6 →6/3=2 ≥2?是(count=1)→ 错误!此时子数组长度是 1,不是 3;
  • i=1:currentSum=12 →12/3=4 ≥2?是(count=2)→ 错误!子数组长度是 2;
  • i=2:currentSum=18 →18/3=6 ≥2?是(count=3)→ 正确;最终你的代码返回 3,但实际满足条件的只有 1 个(长度为 3 的子数组),明显错误。

三、还有一个隐藏风险:整数除法的精度问题

虽然你的代码里 currentSum / k 看似没问题,但其实是 "巧合"------ 因为题目要求 "平均值 ≥ threshold",等价于 currentSum ≥ k × threshold(这是更严谨的转化)。

比如 k=3, threshold=4 → 等价于 currentSum ≥12。如果用 currentSum / k ≥4,当 currentSum=11 时,11/3=3(整数除法截断),判断正确;但如果 threshold=4.3(假设题目是浮点数阈值),整数除法就会出错。不过这道题阈值是整数,所以这个问题没暴露,但第一个循环的判断时机错误是核心 bug

四、修正后的代码(只改 1 处)

把第一个循环里的判断移到循环外面 ------ 等累加完 k 个元素(第一个完整窗口)后,再判断一次即可:

cpp

运行

cpp 复制代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        int currentSum = 0 ;
        int count = 0 ;

        // 第一步:只累加第一个窗口的和,不判断
        for (int i = 0 ; i < k ; i ++ ){
            currentSum += arr[i];
        }
        // 累加完 k 个元素后,再判断第一个窗口(长度=k)
        if( (currentSum / k) >= threshold ){
            count++;
        }

        // 第二步:滑动窗口,后续每个窗口都是长度=k,直接判断
        for (int i = k ; i < n ; i++){
            currentSum = currentSum - arr[i - k] + arr[i];
            if( (currentSum / k) >= threshold ){
                count++;
            }
        }

        return count;
    }
};

五、进一步优化(避免整数除法,更严谨)

如之前的思路,把 currentSum / k >= threshold 转化为 currentSum >= k * threshold,完全避免整数除法的潜在问题(虽然这道题用除法也能过,但养成习惯更好):

cpp 复制代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        int target = k * threshold;  // 转化为整数判断,无精度问题
        int currentSum = 0 ;
        int count = 0 ;

        for (int i = 0 ; i < k ; i ++ ){
            currentSum += arr[i];
        }
        if(currentSum >= target){  // 直接用和判断,更高效
            count++;
        }

        for (int i = k ; i < n ; i++){
            currentSum = currentSum - arr[i - k] + arr[i];
            if(currentSum >= target){
                count++;
            }
        }

        return count;
    }
};

总结

你的核心错误是 第一个循环中 "提前判断"------ 把 "累加过程" 和 "完整窗口判断" 混在一起,导致统计了长度不足 k 的子数组。修正后,只需要在第一个窗口累加完成后判断一次,后续滑动窗口的判断都是正确的(因为滑动后窗口长度始终是 k)。

这道题和 643 题的滑动窗口逻辑一致,重点是要明确:窗口必须是 "完整长度 k" 后,才能做判断!!!!

总结框架

针对 LeetCode 中 "长度固定为 k 的连续子数组" 这类问题(比如 643 题 "最大平均数"、1343 题 "满足阈值的子数组数"),可以提炼出一个 通用滑动窗口框架------ 核心是 "先初始化窗口、再滑动更新窗口、全程处理窗口条件",所有这类题都能套这个模板解决,无需重复思考逻辑。

下面用清晰的步骤 + 代码框架 + 应用示例,帮你彻底掌握:

一、通用框架核心思想

固定窗口长度 k,避免重复计算子数组和:

  1. 初始化窗口 :计算前 k 个元素的和(第一个完整窗口);
  2. 处理第一个窗口:根据题目要求判断第一个窗口是否满足条件(比如求和、计数、找最大);
  3. 滑动更新窗口 :从第 k 个元素开始,每次窗口右移时,减去左侧离开的元素,加上右侧进入的元素(O (1) 时间更新窗口和);
  4. 处理后续窗口:每次更新后,再次判断当前窗口是否满足条件,更新结果;
  5. 返回最终结果:根据题目要求返回统计值、最大值等。

二、通用代码框架(直接复制修改)

cpp 复制代码
class Solution {
public:
    // 函数返回值根据题目调整(比如 int、double)
    返回类型 题目函数名(vector<int>& arr, int k, 其他参数) {
        int n = arr.size();
        int currentSum = 0;  // 核心:当前窗口的和(固定长度 k)
        结果变量;  // 比如 count(计数)、maxSum(找最大)等,根据题目初始化

        // 步骤1:初始化第一个窗口(前 k 个元素的和)
        for (int i = 0; i < k; ++i) {
            currentSum += arr[i];
        }

        // 步骤2:处理第一个窗口(根据题目要求判断)
        处理逻辑;  // 比如:计数+1、更新maxSum等

        // 步骤3:滑动窗口(遍历剩余元素,更新窗口并处理)
        for (int i = k; i < n; ++i) {
            // 窗口右移:去掉左侧离开的元素(arr[i - k]),加入右侧新元素(arr[i])
            currentSum = currentSum - arr[i - k] + arr[i];

            // 步骤4:处理当前窗口(和步骤2的处理逻辑一致)
            处理逻辑;  // 重复步骤2的判断,更新结果变量
        }

        // 步骤5:返回最终结果(可能需要转换类型,比如 sum/k 转 double)
        return 最终结果;
    }
};

三、框架应用示例(对应两道题,看如何套模板)

示例 1:LeetCode 643 题(子数组最大平均数 I)
  • 题目要求:找长度为 k 的子数组的最大平均数(等价于找最大和,再除以 k)
  • 套用框架:
    • 结果变量:int maxSum = currentSum(记录最大窗口和)
    • 处理逻辑:每次窗口和与 maxSum 比较,更新最大值
    • 最终结果:static_cast<double>(maxSum) / k(转 double 避免整数除法)
cpp 复制代码
class Solution {
public:
    double findMaxAverage(vector<int>& nums, int k) {
        int n = nums.size();
        int currentSum = 0;
        // 结果变量:初始化最大和为第一个窗口和
        int maxSum = 0;

        // 步骤1:初始化第一个窗口
        for (int i = 0; i < k; ++i) {
            currentSum += nums[i];
        }

        // 步骤2:处理第一个窗口
        maxSum = currentSum;

        // 步骤3:滑动窗口
        for (int i = k; i < n; ++i) {
            currentSum = currentSum - nums[i - k] + nums[i];
            // 步骤4:处理当前窗口(更新最大和)
            maxSum = max(maxSum, currentSum);
        }

        // 步骤5:返回结果(转 double 计算平均数)
        return static_cast<double>(maxSum) / k;
    }
};
示例 2:LeetCode 1343 题(满足阈值的子数组数)
  • 题目要求:统计长度为 k 的子数组中,平均值 ≥ threshold 的个数(等价于和 ≥ k×threshold)
  • 套用框架:
    • 结果变量:int count = 0(统计满足条件的个数)
    • 处理逻辑:判断窗口和是否 ≥ 目标值(k×threshold),满足则 count++
    • 最终结果:直接返回 count
cpp 复制代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        int currentSum = 0;
        int target = k * threshold;  // 转化为整数判断,避免浮点数
        // 结果变量:计数初始为 0
        int count = 0;

        // 步骤1:初始化第一个窗口
        for (int i = 0; i < k; ++i) {
            currentSum += arr[i];
        }

        // 步骤2:处理第一个窗口
        if (currentSum >= target) count++;

        // 步骤3:滑动窗口
        for (int i = k; i < n; ++i) {
            currentSum = currentSum - arr[i - k] + arr[i];
            // 步骤4:处理当前窗口(满足条件则计数+1)
            if (currentSum >= target) count++;
        }

        // 步骤5:返回结果
        return count;
    }
};

四、框架关键注意点(避免踩坑)

  1. 窗口初始化后再处理 :第一个窗口必须累加完 k 个元素后,再做判断(避免像你之前的错误那样,提前判断长度不足 k 的子数组);
  2. 窗口更新逻辑固定currentSum = currentSum - arr[i - k] + arr[i] 是核心,不管题目怎么变,滑动时都这么写(i - k 是左侧离开的元素索引);
  3. 结果处理灵活调整
    • 找最大 / 最小值:结果变量初始化为第一个窗口的值,后续更新时比较替换;
    • 计数:结果变量初始化为 0,满足条件则累加;
    • 需浮点数:最后一步转换类型(比如 643 题的 static_cast<double>);
  4. 避免冗余计算:用 "窗口和" 替代每次遍历 k 个元素求和,时间复杂度从 O (nk) 降到 O (n),效率翻倍。

五、总结

这类 "固定长度 k 的子数组" 问题,本质都是 "窗口和的维护 + 条件判断",通用框架的核心价值是:

  • 无需重复设计滑动逻辑,直接套模板;
  • 重点只需要关注 "处理逻辑"(步骤 2 和步骤 4),根据题目要求调整即可;
  • 避免踩 "提前判断""整数除法" 等常见坑。

后续遇到类似题目(比如 "长度为 k 的子数组最小和""长度为 k 的子数组中偶数的个数"),都可以直接用这个框架,只改 "结果变量" 和 "处理逻辑" 两部分。

相关推荐
hmbbcsm1 小时前
nginx学习笔记
笔记·学习·nginx
AndrewHZ1 小时前
【图像处理基石】什么是图像处理中的注意力机制?
图像处理·pytorch·深度学习·算法·计算机视觉·注意力机制·通道注意力
知识分享小能手1 小时前
CentOS Stream 9入门学习教程,从入门到精通,Linux操作系统概述 —全面知识点详解(1)
linux·学习·centos
DuHz1 小时前
通感一体化(ISAC)波形设计的实验验证研究——论文阅读
论文阅读·算法·信息与通信·毫米波雷达
CoderYanger1 小时前
递归、搜索与回溯-综合练习:22.优美的排列
java·算法·leetcode·深度优先·1024程序员节
zore_c1 小时前
【C语言】struct结构体内存对齐和位段(超详解)
c语言·开发语言·经验分享·笔记
_不会dp不改名_1 小时前
HCIP笔记6--OSPF域外路由、特殊区域(stub、totally stub)
笔记·hcip
ccnnlxc1 小时前
go语言学习
学习
Freshman小白1 小时前
《项目管理》学堂在线2025网课答案
学习·答案