数组-大小为 K 且平均值大于等于阈值的子数组数目(力扣 1343)

题目链接 点我

思路

基本概念要掌握

子数组:一个或多个连续原数组中元素组成子数组
子序列:在原序列中抽取一个或多个元素组成子序列
即子数组必须连续,子序列不一定连续
作者本人在第一次做题时没有思路就是没有搞清楚这两个概念。

思路一

在了解了子数组的概念后,不知道你有没有了思路?是否感觉题目瞬间简单了。
不就是在数组中找出所有的连续的k个元素的子数组,对于每个子数组求和,再除k与threshold比较。那么有以下几个问题:

  1. 如何找出一个数组所有的连续的k个元素的子数组?
    以我们正常人的思维首先想到的就是遍历,双层循环,第一层循环来表示子数组的开始元素所在位置,第二层循环从开始元素所在位置想后遍历k个元素,不就能够得到所有的k个元素的子数组。
  2. 对每个子数组求和再除k与threshold比较有没有问题?是否可以简单优化?
    作为程序员应该知道整型数据进行除法,如果结果有小数的话,小数会丢失,从而产生误差,所以看到整型除法我们要想办法规避,可以想到除法变乘法;而且对于每个子数组来说,求和是必须的要求,但是每个子数组都要除k,再与threshold比较,很显然可以不用每次除k,直接事先求出k乘threshold的值,再与子数组和比较,这样省去了好多次除法,对代码也算是一种优化。

好了,对于算法题,有了大概思路,我们就应该再草稿纸上或者脑子里,将整个算法过程列出来,这样写代码就是分分钟的问题。以下是我的过程,小伙伴们要自己想哦。想完再来参考。

  1. 需要用到的数据(对应代码数据声明定义)
    (1)存放比较目标的变量 target,即k*threshold
    (2)数组长度len,遍历数组时需要用到,
    (3)存放返回结果的变量num,记录大于等于target的子数组数目
    (4)存放子数组和的变量sum,遍历每个子数组前清0,遍历时计算,遍历完与target比较并更新num

  2. 对于需要初始化的部分数据进行初始化,以及这些数据的作用和使用
    (1)target = k*threshold
    (2)len = array.size()
    (3)num = 0
    (4)在每次遍历前更新sum=0,遍历时sum+=array[i]

  3. 遍历数组
    双层循环,外层循环指定子数组的开始位置,开始位置的范围是0~len-k;内层循环对从开始位置向后的k个元素求和,求和完成后在外层循环对求和结果进行判断处理,更新num。

  4. 返回结果

有了算法过程,代码也就轻而易举可以写出来咯

代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        // 声明
        int num = 0,sum;
        // 计算target
        int target = k*threshold;
        // 计算数组长度
        int len = arr.size();
        // 遍历求和并比较大小
        for(int i=0;i<=len-k;i++){
            // 内层求当前以i开始的k个元素的和
            sum = 0;
            for(int j=i;j<i+k;j++){
                sum+=arr[j];
            }
            // 判断sum是否满足大于等于target的要求
            if(sum >= target){
                num++;
            }
        }
        return num;
    }
};
问题

通过计算复杂度我们发现,双层循环一共要执行(len-k+1)*k次,时间复杂度为O(n^2);空间复杂度由于我们使用的是常数个变量,所以为O(1)。
提交代码会发现,这种解法由于时间复杂度过高会出现超时的现象,如何解决?只能想办法优化这个双层循环了,看看能不能变成O(n)或者其它,如果不优化双层循环的话,还是O(n^2),那就还是超时呗。

思路二

我们要想办法优化双层循环,仔细分析这个双层循环,我们的目标是求子数组的元素之和,但是我们隐约感觉到每次求一个子数组的元素之和时好像用到了之前一次求的结果。如果我们能利用上前一次求和的结果来进行计算,会省去内层循环好多不必要的重复加法。
那么我们就找前一次求和结果和这一次求和结果的关系咯,假设前一次求和结果是sum0,这一次求和结果是sum1,那么经过分析可知sum1等于sum0减去一个开头元素,再加上一个末尾元素。而且减去的这个开头元素和增加的末尾元素在数组中相对位置不变,如果我们指定了添加的末尾元素,减去的开头元素位置也就知道了。我们索性,以添加的末尾元素为基准,进行数组遍历,获得每一次sum。这要求我们得有一个初始sum为基准。
好了,问题解决了,只要更改思路一中求每个子数组元素之和的方式即可。

代码
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        // 声明
        int num = 0,sum=0;
        // 计算target
        int target = k*threshold;
        // 计算数组长度
        int len = arr.size();
        // 找到所有k个元素的子数组,对每个子数组进行判断处理
        // 先找到第一个子数组,进行求和判断
        for(int i=0;i<k;i++){
            sum+=arr[i];
        }
        if(sum >= target) num++;
        // 对于剩下的len-k个子数组,进行遍历处理
        for(int i=k;i<len;i++){
            sum-=arr[i-k];
            sum+=arr[i];
            if(sum >= target) num++;
        } 
        return num;
    }
};
复杂度

通过分析,总共遍历了有len次,所以时间复杂度从O(n^2)变成了O(n);空间复杂度不变,还是O(1)

总结

通过这道数组题目,我们可以获得以下知识:

  1. 不管什么数据结构,求连续的几个元素问题,都可以想到将普通的暴力双层循环改成单层循环。
  2. 对于代码优化,从复杂度方面去分析代码效率低下的主要问题,如本题的双层循环,不改这个双层循环肯定没法优化,针对这个主要问题来进行想办法解决。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值