算法入门(一):滑动窗口 之 固定窗口 (Leetcode 643 / 567 / 438)

算法入门(一):滑动窗口 之 固定窗口 (Leetcode 643 / 1343 / 2090 )

  • 固定窗口
  • [模板1(初始化窗口+滑动窗口 -- 双循环)](#模板1(初始化窗口+滑动窗口 -- 双循环))
  • 模板2(单循环)
  • [LeetCode 643 -- 子数组最大平均数 I](#LeetCode 643 – 子数组最大平均数 I)
  • [LeetCode 1343 - 大小为 K 且平均值大于等于阈值的子数组数目](#LeetCode 1343 - 大小为 K 且平均值大于等于阈值的子数组数目)
  • [LeetCode 2090 半径为 k 的子数组平均值](#LeetCode 2090 半径为 k 的子数组平均值)

固定窗口

例如Leetcode 643 / 1343 / 2090 这类题目,同属于固定窗口

  • 窗口大小起始为 k
  • 每次向右滑动一步,右边加入一个新元素,左边移除一个旧元素
  • 始终保持窗口内始终有 k 个元素

模板1(初始化窗口+滑动窗口 -- 双循环)

cpp 复制代码
int fixedWindow(vector<int>& nums, int k) {
    int n = nums.size();
    // 0.边界处理
    if (n < k) return 0;
    
    // 1. 初始化第一个窗口
    int windowSum = 0;
    for (int i = 0; i < k; i++) {
        windowSum += nums[i];
    }
    
    // 2. 滑动窗口获取最大值/最小值
    int res = windowSum; 
    for (int i = k; i < n; i++) {
        windowSum = windowSum - nums[i - k] + nums[i];
        ans = max(ans, windowSum);  
    }
    return ans;
}

模板2(单循环)

cpp 复制代码
double fixedWindow(vector<int>& nums, int k) {
        int n = nums.size();
        double window_sum = 0;
        double maxSum = INT_MIN;
        
        for(int i = 0;i<n;i++){
        		 //1. 进入窗口
            window_sum +=nums[i];
            //2. 窗口不足k时,继续
            if(i < k - 1){
                continue;
            }
            //3. 更新答案
            maxSum = max(maxSum,window_sum);
            //4. 离开当前窗口,去掉左边的元素,为下一个窗口的右边元素做准备
            window_sum -=nums[i- k + 1];
        }
        return maxSum;
    }

LeetCode 643 -- 子数组最大平均数 I

LeetCode 643 -- 子数组最大平均数 I

固定窗口的母题,只是在求和的基础上加了一步求平均数。

注意答案要求返回double。

写法一(双循环):

cpp 复制代码
double findMaxAverage(vector<int>& nums, int k) {
        int n = nums.size();
        double window_sum = INT_MIN;
        for(int i = 0;i<k;i++){
            window_sum += nums[i];
        }
        double maxAve = window_sum / k;
        for(int i = k;i<n;i++){
            window_sum = window_sum - nums[i-k] + nums[i];
            maxAve = max(maxAve,window_sum / k);
        }
        return maxAve;
    }

写法二(单循环):

cpp 复制代码
double findMaxAverage(vector<int>& nums, int k) {
        int n = nums.size();
        double window_sum = 0;
        double maxSum = INT_MIN;//这里一定要写INT_MIN而不是0,因为单循环方法没有初始化窗口!
        for(int i = 0;i<n;i++){
            window_sum +=nums[i];
            if(i < k - 1){
                continue;
            }
            maxSum = max(maxSum,window_sum);
            window_sum -=nums[i- k + 1];
        }
        return maxSum / k;
    }

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

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

Leetcode643的进阶版。

  • 为每一个窗口添加阈值条件判断
  • 注意 窗口平均值 大于等于 阈值,可以转换为 窗口总和 大于等于 阈值 * 数目

以下由上题的写法一(双循环)改写而来:

cpp 复制代码
int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int n = arr.size();
        int res = 0;

        int window_sum = 0;
        for(int i = 0;i<k;i++){
            window_sum +=arr[i];
        }
        if(window_sum >= threshold * k) res++;

        for(int i = k;i<n;i++){
            window_sum +=arr[i];
            window_sum -=arr[i-k];
            if(window_sum >= threshold * k) res++;
        }

        return res;
    }

LeetCode 2090 半径为 k 的子数组平均值

LeetCode 2090 半径为 k 的子数组平均值

这道题相较于上面两道题多了一些思考的部分。

  • 相较于单循环,双循环写法,需要判断窗口大小和数组长度的关系,也就是边界处理。
  • 为什么前面两道题没有写边界判断?因为题目给了k<n的条件,也就是窗口长度小于数组长度,而这道题没有。
  • 该题的数组大小是10^5,需要考虑window_sum的类型,定义为int是否足够?是否需要定义为long long?

写法一(双循环):

cpp 复制代码
vector<int> getAverages(vector<int>& nums, int k) {
        int n = nums.size();
        long long window_sum = 0;
        vector<int> res(n,-1);

        //0. 边界处理
        if(2*k + 1 > n) return res;

        //1. 第一个窗口
        for(int i = 0;i<2*k+1;i++){
            window_sum += nums[i];
        }
        res[k] = window_sum / (2 * k + 1);

        //2. 后续的窗口
        for(int i = 2*k+1 ;i<n;i++){
            window_sum += nums[i];
            window_sum -= nums[i - (2*k+1)];
            res[i-k] = window_sum / (2 * k + 1);
        }
        return res;
    }

写法二(单循环):

cpp 复制代码
vector<int> getAverages(vector<int>& nums, int k) {
        int n = nums.size();
        long long window_sum = 0;
        vector<int> res(n,-1);

        for(int i = 0;i<n;i++){
            window_sum += nums[i];
            if(i<2 * k){
                continue;
            }
            res[i-k] = window_sum/(2 * k +1);
            window_sum -= nums[i- 2 * k];
        }
        return res;
    }