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

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个则跳过退出步骤以及统计步骤。

代码:

cpp 复制代码
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 和两个整数 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 的值)。

思路:

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

代码:

cpp 复制代码
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作初始值。

代码:

cpp 复制代码
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的算法思想对每一个子串使用哈希数表示,这里的具体算法如下:

cpp 复制代码
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;
    }
};
相关推荐
杜杜的man13 分钟前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
小沈熬夜秃头中୧⍤⃝29 分钟前
【贪心算法】No.1---贪心算法(1)
算法·贪心算法
木向1 小时前
leetcode92:反转链表||
数据结构·c++·算法·leetcode·链表
阿阿越1 小时前
算法每日练 -- 双指针篇(持续更新中)
数据结构·c++·算法
skaiuijing1 小时前
Sparrow系列拓展篇:对调度层进行抽象并引入IPC机制信号量
c语言·算法·操作系统·调度算法·操作系统内核
Star Patrick2 小时前
算法训练(leetcode)二刷第十九天 | *39. 组合总和、*40. 组合总和 II、*131. 分割回文串
python·算法·leetcode
武子康3 小时前
大数据-214 数据挖掘 机器学习理论 - KMeans Python 实现 算法验证 sklearn n_clusters labels
大数据·人工智能·python·深度学习·算法·机器学习·数据挖掘
m0_571957586 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
pianmian18 小时前
python数据结构基础(7)
数据结构·算法
考试宝9 小时前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物