滑动窗口学习笔记(基础部分)

643. 子数组最大平均数 I

题目:

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10-5 的答案都将被视为正确答案。

示例 1:

输入:nums = [1,12,-5,-6,50,3], k = 4
输出:12.75
解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

思路:

首先设置变量ans和cur,因为数组中可能包含负值,所以ans变量的初值便不能设置为0,设置为一个相对较小的负数,遍历整个字符串,遵循三个步骤:进入,统计,退出,这道题的进入即:将当前遍历的元素加入cur,如果当前便利的数量不足k-1个则跳过退出步骤以及统计步骤。

代码:

class Solution {
public:
    double findMaxAverage(vector<int>& nums, int k) {
        double ans = -2e5, cur = 0;
        int n = nums.size();
        for(int i = 0; i < n; i++){
            cur += nums[i];
            if(i < k - 1){
                continue;
            }
            ans = max(cur / k, ans);
            cur -= nums[i - k + 1];
        }
        return ans;
    }
};

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

题目:

给你一个整数数组 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 的值)。

思路:

首先设置变量ans和cur表示符合条件子数组个数以及当前子数组的和,遍历整个数组依旧遵循三个步骤:进入,统计,退出。进入即:将当前遍历的元素加入cur,如果当前遍历的数目不足k个就跳过统计和退出步骤。

代码:

class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        int ans = 0, cur = 0;
        int n = arr.size();
        for(int i = 0; i < n; i++){
            cur += arr[i];
            if(i < k - 1){
                continue;
            }
            int cur_mean = cur / k;
            if(cur_mean >= threshold){
                ans++;
            }
            cur -= arr[i - k + 1];
        }
        return ans;
    }
};

2653. 滑动子数组的美丽值

题目:

给你一个长度为 n 的整数数组 nums ,请你求出每个长度为 k 的子数组的 美丽值 。

一个子数组的 美丽值 定义为:如果子数组中第 x 小整数 是 负数 ,那么美丽值为第 x 小的数,否则美丽值为 0 。

请你返回一个包含 n - k + 1 个整数的数组,依次 表示数组中从第一个下标开始,每个长度为 k 的子数组的 美丽值 。

  • 子数组指的是数组中一段连续 非空 的元素序列。 

示例 1:

输入:nums = [1,-1,-3,-2,3], k = 3, x = 2
输出:[-1,-2,-2]
解释:总共有 3 个 k = 3 的子数组。
第一个子数组是 [1, -1, -3] ,第二小的数是负数 -1 。
第二个子数组是 [-1, -3, -2] ,第二小的数是负数 -2 。
第三个子数组是 [-3, -2, 3] ,第二小的数是负数 -2 。

思路:

这道题的难点在于怎么去找当前窗口内第x小的数作为美丽值 ,若使用sort对新数组排序后取下表为x - 1的元素插入到结果数组中固然可行但是时间复杂度过高不能通过全部样例。

我们使用一个额外的数组记录当前窗口内个元素出现的次数,将第x小的条件作为一个整型变量index,然后从0只开始向后遍历,将index减去小于0的元素出现的次数,直到index <= 0时我们就遍历到了第x小且为负数的元素(第x小这个条件就代表着有x-1个元素为负数),所以我们直接赋值到结果数组res,为了方便我们在遍历nums数组元素时将每个元素加上add=50,因为nums中的元素的范围是【-50, 50】,所以我们控制元素的范围为【0, 100】,这样每次寻找第x小的元素时也就是遍历元素出现次数数组的【0, 50】也就是负数的区间,若没有找到美丽值,即index > 0就以0作初始值。

代码:

class Solution {
public:
    vector<int> getSubarrayBeauty(vector<int>& nums, int k, int x) {
        int n = nums.size();
        int add = 50;
        vector<int> nums_new(add * 2 + 1, 0);
        vector<int> res(n - k + 1, 0);
        
        // 初始化第一个窗口
        for(int i = 0; i < k; i++){
            nums_new[nums[i] + add]++;
        }
        
        // 处理第一个窗口的美丽值
        int index = x;
        for(int j = 0; j <= add; j++){
            index -= nums_new[j];
            if(index <= 0){
                res[0] = j - add;
                break;
            }
        }
        
        // 滑动窗口
        for(int i = k; i < n; i++){
            // 添加新元素
            nums_new[nums[i] + add]++;
            // 移除旧元素
            nums_new[nums[i - k] + add]--;
            
            // 重新计算美丽值
            index = x;
            for(int j = 0; j <= add; j++){
                index -= nums_new[j];
                if(index <= 0){
                    res[i - k + 1] = j - add;
                    break;
                }
            }
        }
        
        return res;
    }
};

1297. 子串的最大出现次数

题目:

给你一个字符串 s ,请你返回满足以下条件且出现次数最大的 任意 子串的出现次数:

  • 子串中不同字母的数目必须小于等于 maxLetters 。
  • 子串的长度必须大于等于 minSize 且小于等于 maxSize 。

示例 1:

输入:s = "aababcaab", maxLetters = 2, minSize = 3, maxSize = 4
输出:2
解释:子串 "aab" 在原字符串中出现了 2 次。
它满足所有的要求:2 个不同的字母,长度为 3 (在 minSize 和 maxSize 范围内)。

 思路:

这道题我们需要注意第二个条件:

  • 子串的长度必须大于等于 minSize 且小于等于 maxSize 。

 这句话也就代表着符合条件的minSize长度的子串也必定是符合条件的maxSize长度子串的子串,二者属于包含的关系,所以我们只需要找到符合条件的窗口大小为minSize的子串即可。

为了记录每个窗口对应的子串对应的出现次数,我们需要对每一个子串进行标记,这就需要用到rabin-karp的算法思想对每一个子串使用哈希数表示,这里的具体算法如下:

Hash[s] = (s[0] * base ^ (minSize - 1) + s[1] * base ^ (minSize - 2) + ... + s[minSize - 1] * base ^ 0) % mod

首先我们对初始窗口进行处理:

初始窗口对应子串的哈希值为:初始rabin值 * 初始权重 + s[i],通过遍历后续初始窗口的元素,我们会得到rabin的值在不断的乘以初始权重,这样使得最左侧元素对应的权重在不断增加:

第0位: rabin(0) = rabin * base + s[0] = s[0]
第1位: rabin(1) = rabin(0) * base + s[1]
                 = s[0] * base + s[1]
第2位: rabin(2) = rabin(1) * base + s[2]
                 = s[0] * base * base + s[1] * base + s[2] 
                 = s[0] * base ^ 2 + s[1] * base ^ 1 + s[2] * base ^ 0

当我们在处理滑动窗口是我们需要添加新元素,去除旧元素,而去除旧元素我们需要得到旧元素所对应的权重值也就是base的多少次方,该权重值跟窗口大小有关,为次幂即是窗口的大小,所以我们即得到了添加新元素以及去除旧元素对窗口哈希值得处理;

using LL = long long;

class Solution {
public:
    int maxFreq(string s, int maxLetters, int minSize, int maxSize) {
        int n = s.size();
        int res = 0, rabin = 0, mod = 1e8, base = 26, base_left = base;
        int unique_count = 0;
        // 当前遍历字符的出现次数
        unordered_map<char, int> unique_nums;
        // 当前窗口子串出现次数
        unordered_map<int, int> string_count;

        // 计算窗口最左侧元素的权重大小,后续移出旧元素时需从rabin中间去
        for (int i = 0; i < minSize - 1; ++i) {
            base_left = (LL)base_left * base % mod;
        }

        // 计算初始窗口的哈希值
        for (int i = 0; i < minSize; ++i) {
            unique_nums[s[i]]++;
            if (unique_nums[s[i]] == 1) {
                unique_count++;
            }
            rabin = (LL)rabin * base + (s[i] - 'a');
            rabin %= mod;
        }

        if (unique_count <= maxLetters) {
            string_count[rabin]++;
            res = max(res, string_count[rabin]);
        }

        // 滑动窗口
        for (int i = minSize; i < n; ++i) {
            // 添加新元素
            unique_nums[s[i]]++;
            if (unique_nums[s[i]] == 1) {
                unique_count++;
            }
            // 去除旧元素
            unique_nums[s[i - minSize]]--;
            if (unique_nums[s[i - minSize]] == 0) {
                unique_count--;
            }
            // 更新窗口哈希值:先乘以基础权重base然后加上新元素
            // 在添加完新元素的窗口移出旧元素,减去左侧元素对应的权重base-left
            rabin = (LL)rabin * base + (s[i] - 'a');
            rabin %= mod;
            rabin = (rabin - (LL)(base_left * (s[i - minSize] - 'a')) % mod + mod) % mod;

            if (unique_count <= maxLetters) {
                string_count[rabin]++;
                res = max(res, string_count[rabin]);
            }
        }

        return res;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值