六月集训(第06天) —— 滑动窗口

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了六月集训(第06天) —— 滑动窗口相关的知识,希望对你有一定的参考价值。

前言

        此为《英雄算法联盟:算法集训》的内容,具体内容详见:知识星球:英雄算法联盟 - 六月集训。加入星球后,即可享用星主 CSDN付费专栏 免费阅读 的权益。
        欢迎大家积极在评论区留言发表自己的看法,知无不言,言无不尽,养成每天刷题的习惯,也可以自己发布优质的解题报告,供社区一同鉴赏,吸引一波自己的核心粉丝。
        希望大家先自己思考,如果实在没有想法,再看下面的算法思路,如果有思路但是写不出来,可以参考朋友圈中其他人的代码,总有一款是适合你的,关注一下他,取其之长,补给之短。
        今天集训的内容是:滑动窗口
        今天的题前三题是滑动窗口的经典题,基本属于套模板。第四题,利用差分的思想,巧妙的化解第二层循环,代码较短,思考维度较深,建议好好想一下,实在想不出来,睡觉前再想想。

一、练习题目

题目链接难度
1984. 学生分数的最小差值★☆☆☆☆
1763. 最长的美好子字符串★★☆☆☆
2269. 找到一个数字的 K 美丽值★★☆☆☆
995. K 连续位的最小翻转次数★★★★☆

二、算法思路

1、学生分数的最小差值

        (1)设定两个指针 i 和 j 分别代表窗口的左右区间;
        (2)一开始是一个空窗口,所以 i = 0, j = -1;
        (3)不断滑动窗口的右区间的值,当窗口长度大于 k 时,滑动左区间的值;
        (4)当窗口长度为 k 时,统计最大值和最小值的差值,取所有的窗口差值中的最小值就是我们要求的答案了。

class Solution 
public:
    int minimumDifference(vector<int>& nums, int k) 
        sort(nums.begin(), nums.end());
        int i = 0, j = -1;
        int size = nums.size(); 
        int ans = 1000000000;
        while(j < size - 1) 
            ++j;
            while(j - i + 1 > k) 
                ++i;
            
            if(j - i + 1 == k) 
                ans = min(ans, nums[j] - nums[i]);
            
        
        return ans;
    
;

2、最长的美好子字符串

        (1)要求最长,所以我们可以从大到小枚举长度 l;
        (2)然后利用第一题的滑动窗口,求出一个满足条件的解就是我们要求的答案了。

class Solution 
    int code(char c) 
        if( c >= 'a' && c <= 'z') 
            return c - 'a';
        
        return c - 'A' + 26;
    
public:
    string longestNiceSubstring(string s) 
        int l, i, j, k;
        int size;
        int hash[52];
        for(l = s.size(); l > 0; --l) 
            i = 0;
            j = - 1;
            size = s.size();
            memset(hash, 0, sizeof(hash));
            while(j < size - 1) 
                ++j;
                ++hash[ code(s[j]) ];
                while(j - i + 1 > l) 
                    --hash[ code(s[i]) ];
                    ++i;
                
                if(j - i + 1 == l) 
                    for(k = 0; k < 26; ++k) 
                        if( hash[k] && !hash[k+26] ) 
                            break;
                        else if( !hash[k] && hash[k+26] ) 
                            break;
                         
                    
                    if(k == 26) 
                        return s.substr(i, j - i + 1);
                    
                
            
        
        return "";
    
;

3、找到一个数字的 K 美丽值

        (1)这个题由于数组范围非常小,转换成字符串也就 9 个字符,所以不用滑动窗口也可以直接模拟过掉,如果想练习滑动窗口也可以用滑动窗口来做。

class Solution 
public:
    int divisorSubstrings(int num, int k) 
        int f[10];
        f[0] = 1;
        for(int i = 1; i < 10; ++i) 
            f[i] = f[i-1] * 10;
        
        vector <int> stk;
        int N = num;
        int ret = 0;
        while(num) 
            stk.push_back(num % 10);
            num /= 10;
        
        reverse(stk.begin(), stk.end());
        int i = 0, j = -1;
        int size = stk.size();
        int s = 0;
        while(j < size - 1) 
            ++j;
            s = s * 10 + stk[j];
            while(j - i + 1 > k) 
                ++i;
                s %= f[k];
            
            if(j - i + 1 == k) 
                if(s && N % s == 0) 
                    ++ret;
                
            
        
        return ret;
    
;

4、K 连续位的最小翻转次数

        (1)如果数据量较小,比如长度为 1000 的情况下,只需要从左往右,找到第一个为 1 的位置,将它后续 k 个位置都进行翻转。顺便修改对应的位置上的值,0变1,1变0,直到后面少于 k 个时,返回 -1;如果正好能够翻转完毕,则返回翻转次数。这个次数一定是最小的。时间复杂度为 O(nk)。这是一个贪心的思想。
        (2)那么如果我们还是采用这种思想,可以简化翻转这一步操作将它降为 O(1)。
        (3)我们需要实现以下几个接口:
                (3.1)获取第 i 位的值;
                (3.2)从 i 开始的 k 位执行翻转操作;
        (4)首先来看翻转操作,用一个长度为 n 的数组 a,记录当前这个位置的操作次数,比如从 i 开始的 k 位执行翻转操作,其实就是对于两个点执行异或,哪两个点呢? a[i] 和 a[i+k] 这两个位置(第 i 个位置加一,第 i+k 个位置减一,由于只需要讨论奇偶性,所以直接采用异或即可)。
        (5)获取第 i 个位置,其实就是求 (nums[i] + a[0] + … + a[i]) & 1,这一步可以通过一次扫描求出来。所以只需要一次扫描就可以把整个贪心的过程给计算出来了。

class Solution 
    int a[100010];
public:
    int minKBitFlips(vector<int>& nums, int k) 
        memset(a, 0, sizeof(a));
        int ans = 0, sum = 0;
        for(int i = 0; i < nums.size(); ++i) 
            sum ^= a[i];
            sum &= 1;
            if( ( (sum + nums[i]) & 1) == 0 ) 
                if(i + k > nums.size()) 
                    return -1;
                
                a[i] ^= 1;
                a[i+k] ^= 1;
                ++ans;
                sum ^= 1;
            
        
        return ans;
    
;

以上是关于六月集训(第06天) —— 滑动窗口的主要内容,如果未能解决你的问题,请参考以下文章

七月集训(第06天) —— 滑动窗口

五月集训 (第06天) —— 滑动窗口

六月集训(第03天) —— 排序

六月集训(第16天) —— 队列

六月集训(第04天) —— 贪心

六月集训(第01天) —— 数组