专题二_滑动窗口_算法专题详细总结

news2024/11/17 11:34:31

目录

滑动窗口,引入:

滑动窗口,本质:就是同向双指针;

1.⻓度最⼩的⼦数组(medium)

1.解析:给我们一个数组nums,要我们找出最小子数组的和==target,首先想到的就是暴力解法

1)暴力:

2)优化,滑动窗口:

1.进窗口

2.出窗口

3.更新值

2.⽆重复字符的最⻓⼦串(medium)

1)仍然是暴力解法:

2)优化:

进窗口:hash[s[right]]++;

出窗口: while(hash[s[right]]>1)    hash[s[left++]]--;

更新len:   right-left+1;

3.最⼤连续1的个数III(medium)

1)暴力:

2)优化:

进窗口:if(nums[right]==0) _k++; 

出窗口:while(_k>k)  if(nums[left++]==0)   _k--;

更新len:len=max(len,right-left+1);

总结:

4.将x减到0的最⼩操作数(medium)

解析:

1)暴力:

2)优化:小demo

进窗口:ret+=nums[right]

出窗口:while(ret>target) ret-=nums[left++]

更新len: if(ret==target)  len=max(len,right-left+1);

总结:

5.⽔果成篮(medium)

解析:

1)暴力:

2)优化:

进窗口:if (hash[fruits[right]]++ == 0) k++; 只要遇到 没遇见种类的水果时,k才++出窗口:while (left < n && k > 2)    if (--hash[fruits[left++]] == 0) k--; 当这种水果数量变成0了,也就说明这种水果不存在了,种类k-1更新len:len = max(len, right - left + 1);

总结:

6.找到字符串中所有字⺟异位词(medium)

解析:

1).暴力:

2).小优化:

大优化:

进窗口:if(++hash2[s[right]]<=hash1[s[right]]) count++;

//分为两步,先是hash2[s[right]]++,然后判断是不是小于等于hash1[s[right]],是就说明,是一个有效字符

出窗口: if(right-left+1>n&&hash2[s[left]]--<=hash1[s[left++]]) count--;

判断减掉的这个 hash2[s[left]]--,是不是小于等于hash1[s[right]],是就说明,是一个有效字符

更新left: if(count==n) ret.push_back(left);

总结:

7.串联所有单词的⼦串(hard)

解析:

1).暴力

2).优化:

进窗口:string str = s.substr(right, len);                //进窗口                hash2[str]++;                if(hash1.count(str) && hash2[str] <= hash1[str]) count++; //记录有效字符串的个数

出窗口:if(right - left + len > m * len)                 {                    string _str = s.substr(left, len);                    if(hash1.count(_str) && hash2[_str] <= hash1[_str]) count--;                    hash2[_str]--;                    left += len;                }

更新left:if(count == m) ret.push_back(left);

总结:

8.最⼩覆盖⼦串(hard)

解析:

1).暴力:

2).优化:

进窗口:      if (hash1.count(s[right]) && ++hash2[s[right]] == hash1[s[right]]) count++;

出窗口: while (len>m&&count == hash1.size())        {            if (len>m&&len > right - left + 1)            {                len = right - left + 1;                _left = left;            }            if (hash1.count(s[left]) && --hash2[s[left]] < hash1[s[left++]]) count--;            while (left < n && hash2[s[left]] == 0) left++;//跳过非种类字符        }

更新len:if(len>n) return "";    return s.substr(_left, len);


滑动窗口,引入:

顾名思义,就是在同向双指针的条件下创建的一个窗口大小,让两个指针left 和 right 从左向右移动。

滑动窗口,本质:就是同向双指针;

两个指针都可以做到不回退都能同向进行的时候,就可以采用滑动窗口
怎么用,定义left=0,right=0,来进窗口,right++;出窗口的时候更新left++;知道right==n时结束
对于滑动窗口的效率提升,是利用数组的单调性,就比如要枚举left-right之间数字相加之和==target,那么sum+=nums[right] 到某个值后>target了,就不用继续加后面的值了,这样就不用枚举后面所有的情况,避免了很多没有必要的枚举

时间复杂度:代码可能是两层嵌套循环,但是实际上就只是right指针和left指针从左走到右,不回头的操作,那么就是最大时间复杂度也只是n+n次=2n 是O(N)级别

1.⻓度最⼩的⼦数组(medium)

题目的意思就是找出最短子数组的和==target ,这是最经典的滑动窗口的入门问题了,可以现在就动手解决一下,如果没明白再看解析。

1.解析:给我们一个数组nums,要我们找出最小子数组的和==target,首先想到的就是暴力解法

1)暴力:

就是从第一个元素开始进行相加,遍历后面的每一个元素,直到right移动到n-1的位置,left++,right回头重新开始遍历相加,然后遇到和sum==target的时候就更新len,最后返回最短的len,但是这种做法想都不用想,时间复杂度是O(N^2) 绝对会超时

2)优化,滑动窗口:

再讲滑动窗口做法前,我们要考虑为什么可以采用滑动窗口,为什么选择滑动窗口。

任何解法都是在暴力下来做优化,有了暴力的解法,我们可以看出,只要固定left ,让right++,使sum和>= target 我们就可以停止right的移动,因为right继续后移,sum继续加和,是没有意义的。所以,我们这个时候停止righ的移动,那么right要会退吗?

答案是不用,因为sum已经记录了left 到 right 之间的和,我们没有必要让right回退后重新计算之间数字的和,而是 sum-=nums[left] 然后left++;这样就又可以保证left移动后,并且到right之间的值里面被算出来。

这样我们能够发现,left 和 right 两个指针并没有进行回退,而是一直向前走,我们称这种同向的双指针,为 “滑动窗口” 。

所以直到right==n时就结束移动。这样在不满足边界条件的前提下进行跟新len 和 让left++。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n=nums.size();
        int len=INT_MAX,sum=0;
        for(int left=0,right=0;right<n;right++)
        {
            //进窗口
            sum+=nums[right];

            //出窗口
            while(sum>=target)
            {
                //为什么要先更新len,因为如果进来的条件是sum==target 那么就是要先更新最小值,最后在left++
                len=min(len,right-left+1);
                sum-=nums[left++];
            }
        }

        return len==INT_MAX?0:len;
    }
};

通过上面,就已经可以分析出,“滑动窗口”系列就是:

1.进窗口
2.出窗口
3.更新值

2.⽆重复字符的最⻓⼦串(medium)

如果是按照leetcode顺序刷的话,有多少人都死在了这题上面,其实如果了解了“双指针”->"滑动窗口"的过度,能很简单解决这题!!!

解析:

1)仍然是暴力解法:

不含有重复字符,那么就考虑到从第一个元素开始遍历,存到一个字符串str里,如果遇到了重复字符,就回退right,让left++,在从头开始遍历,当然这样的O(N^2) 绝对会超时;

2)优化:

有暴力延伸出双指针,定义left 和 right 那么开始遍历的时候,right++,直到遇到重复的字符的时候,我们就让left++,直到跳过  跟s[right] 重复的同一个字符,

这时就可以写出,定义一个hash表来记录每个字符出现的次数,>2 就说明重复

进窗口:hash[s[right]]++;

出窗口: while(hash[s[right]]>1)    hash[s[left++]]--;

更新len:   right-left+1;

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size()==1) return 1;
        unordered_map<char,int> hash;
        int n=s.size();
        int len=0;
        for(int left=0,right=0;right<n;right++)
        {
            //进窗口
            hash[s[right]]++;
            //出窗口
            while(hash[s[right]]>1)
                hash[s[left++]]--;
            len=max(len,right-left+1);
        }
        return len;
    }
};

总结:滑动窗口题目模板都大差不差,这题因为是考虑去掉重复字符,那么可以在每次循环结束都来判断最大的len,也不用在while里判断,因为可能回造成s字符串没有重复的字符,那么就进不去while

3.最⼤连续1的个数III(medium)

只要不对数组直接进行调整,移动什么的,其实都是比较简单的~ 因为下标都是固定的

解析:

1)暴力:

我在这题也卡了很久,暴力无非就是从第一个元素开始,遇到1,right++,遇到0,k--,直到k=0,然后更新len=right - left + 1;然后left++,right回头 重新遍历,重新跟新left,那么绝对超时O(N^2);

2)优化:

通过暴力看也看出,每次right走到k==0 的时候都要回头重新遍历,那么这么多次重新遍历都是无效的,我们只需要用len记录后,left++,直到越过一个0,k+=1,说明right就又可以往后移动到一个为0 的位置,当遇到第二个0的时候right停下来,我们就再次更新len,就可以得到最长的len

这里要说明的是,我们可以定义一个_k ,来记录遇到0的个数,当_k==k 的时候,right仍然可以向后移动,这点一定要想清楚,只要当_k>0 ,那么边界条件被打破,这个时候就要left++,当nums[left]==0 的话,移动过去后,_k--;

仍然是:

进窗口:if(nums[right]==0) _k++; 

出窗口:while(_k>k)  if(nums[left++]==0)   _k--;

更新len:len=max(len,right-left+1);

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int len=0;
        for(int left=0,right=0,_k=0;right<nums.size();right++)
        {
            if(nums[right]==0) _k++;
            while(_k>k) if(nums[left++]==0) _k--;
            len=max(len,right-left+1);
        }
        return len;
    }
};

总结:

滑动窗口,要从暴力推导到优化还是比较好想的,滑动窗口,是保证两个指针left跟right依次往前运动,不后退,保证了效率的提升,不用产生无用的操作。
这题就是1.先要保证进窗口,//进窗口,一定要在前面
            if(nums[right]==0) _k++;
这个就是进窗口的条件,当nums[right]==0  _k++;

2.那么现在考虑出窗口,while(_k>k) 在这个循环条件里面进行出窗口,其实仔细一些,如果在_k==k 时出窗口是不完美的,你还要保证nums[right+1]这个时候也是等于0 的。所以条件过于复杂苛刻,不便于实现,那么在while里面进行出窗口实际上就是让left++ 只是多加了一个if在满足nums[left++]==0 的条件下,就_k-- 来实现出窗口,这样就又可以让right继续往后移动!!!

4.将x减到0的最⼩操作数(medium)

这里面藏着一个小demo ,如果想不上去,这题简直无脑到直逼困难题!!!没有人会一直写代码,列举所有左右的情况,然后去相减吧,题目的意思就有点牵着别人走了。

解析:

1)暴力:

暴力属实无脑,开始先减减左边,然后减减右边,可以用dfs+回溯来解决,但是压栈太多,题目数据大,肯定会爆,直到x被减为0,那么就返回被更新的次数,如果只是单纯的这么写,那简直不敢想代码量。

2)优化:小demo

我们可以看出,要用最小的操作来求出两边之和为x , 那么逆向过来,是不是就是用最大的操作次数 求出的和==sum-x;

那么翻译过来后就明白了,是在数组里面找出最大操作次数的和==sum - x ,那么我们就令

target == sum - x ,那么这题就跟第一题一样了。

仍然是:

进窗口:ret+=nums[right]
出窗口:while(ret>target) ret-=nums[left++]
更新len: if(ret==target)  len=max(len,right-left+1);
class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sum=0;
        for(auto e : nums) sum+=e;
        sum-=x;
        if(sum<0) return -1;
        int len=-1,ret=0;
        for(int left=0,right=0;right<nums.size();right++)
        {
            //进窗口
            ret+=nums[right];
            //出窗口
            while(ret>sum) ret-=nums[left++];
            //更新len
            if(ret==sum)  len=max(len,right-left+1);
        }
        return len==-1?-1:nums.size()-len;
    }
};

总结:

1.这题有很多关键点,如果只是按照题目要求来进行描写,x减一个左数,或减一个右数,实在是过于麻烦和复杂,时间复杂度绝对超时,那么就要逆向思维;

2.我们要求最短的操作数,那么就是在左右两个位置各找一堆数字的和正好是等于x的,加入所有数字和是sum,那么反过来的意思就是在这左右两堆数字中间的这堆数 之和正好是 sum - x 且要求他的长度是最长的,那么这个意思就立马变简单了,那么我们就可以采用滑动窗口,从最左边开始查找,一直找到最长串的数组和为sum - x 就能达到最好的目的,那么最后返回值就是nums.size()-len

3.细节问题:sum - x可能<0 那么就直接返回 -1
ret+=nums[right] 不用在乎ret < sum 的情况,因为while()里面一定会进行解决ret>sum 的问题  最后就是ret == sum 要单独拿出来进行更新len值

5.⽔果成篮(medium)

这题相对来说,还是比较简单的,有了上面几题的经验之后,这题只需要记录水果的种类个数不超过2,就可以一直装水果,然后记录最长子数组个数。

题目意思就是最多只能装两个不同种类的水果,求最大子数组的长度。

解析:

1)暴力:

那么就设置两个指针,left和right,从头开始遍历,设置一个计数器k,直到right遍历到第三个种类的水果时,停止,这个时候跟新len 的长度,在让left++,right回头重新进行遍历,那么绝对会超时,O(N^2);

2)优化:

就是设置left和right 从头开始遍历,当right遍历到第三个种类的时候,就不用回退,只需要left++直接跳过一个种类的水果的时候,这个right就可以进行往前移动,然后重复上面的操作,就不停的更新len,就可以得到最大长度的len

仍然是:

进窗口:if (hash[fruits[right]]++ == 0) k++; 只要遇到 没遇见种类的水果时,k才++
出窗口:while (left < n && k > 2)    if (--hash[fruits[left++]] == 0) k--; 当这种水果数量变成0了,也就说明这种水果不存在了,种类k-1
更新len:len = max(len, right - left + 1);
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
	int len = 0;
	int n = fruits.size(), k = 0;
	unordered_map<int,int> hash;
	for (int left = 0, right = 0, tmp = 0; right < n; right++)
	{
        //进窗口
		if (hash[fruits[right]]++ == 0) k++;
        //出窗口
		while (left < n && k > 2)
			if (--hash[fruits[left++]] == 0) k--;
        //更新len
		len = max(len, right - left + 1);
	}
	return len;
}
};

总结:

滑动窗口,这一题就是在hash里面最多只能存放两种水果,

1.暴力,那么就是从第一个开始,依次加入水果,直到超过三个,就开始计算长度,left++ right在从头开始计算,想都不要想,这样的暴力绝对超时

2.优化:滑动窗口,因为考虑到,right走到第三种水果的时候,left就开始++,只需要减去最开始left 的这个水果,那么要设置一个计数器k,因为在最开始,我一直以为,hash减到0了hash.size() 就会变小,但其实不会,任然存在,并且也可以变成负数,所以要采用计数器k,if (hash[fruits[right]]++ == 0) k++;
存在新水果,k++;
if (--hash[fruits[left++]] == 0) k--;
超过水果个数k,那么就只要最开始left水果变成0,k就--,这样right就可以继续移动,
最后一直更新len的值就行

6.找到字符串中所有字⺟异位词(medium)

题目意思就是在s里找p,p的顺序是任意的,但是要包含所有字符,返回s里面所有满足条件的字符下标。没有思路的话,可以先试试暴力枚举:

解析:

1).暴力:

暴力枚举其实也有难度,比如要先讲p排序,不然要列举出p的各种顺序肯,太复杂,对比不过来,然后设置str+=s[right],长度len=right-left+1   ==   p.size(); 然后对str进行排序,在判断str==p? 如果等于就存入str的下标,否则就跳过,left和right都++;

2).小优化:

用两个hash表来比较纯如的字符是否完全相等,如果是,就加入left;但是这样会有一个用例超时,说明还可以继续优化。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int n=s.size(),m=p.size();
        unordered_map<char,int> hash1;for(auto e : p) hash1[e]++;
        unordered_map<char,int> hash2;
        vector<int> v;
        for(int left=0,right=0;right<n;right++)
        {
            hash2[s[right]]++;
            while(right-left+1>m) hash2[s[left++]]--;
            //判断两个字符数组是否相等
            
            if(hash1.count(s[right]))
            {
                int f=0;
                for(int i=0;i<m;i++)
            {
                if(hash2[p[i]]!=hash1[p[i]])
                {
                    f=1;
                    break;
                }
            }
            if(f==0) v.push_back(left);

            }

        }
        return v;
    }
};

大优化:

其实对于暴力解,要排序str,p 时间复杂度都是很高的代价,并且,每个字串str都要进行判断,真的太无效了,比如明知道str=“bae” p="abc",明知道二者不可能相等,还非要去比较,那这样显得太呆了,所有我们就要设置一个计数器count 来记录在这个固定的滑动窗口里面 有多少个有效的字符。

因为前面题目都是不固定长的窗口大小,这里是固定长的窗口大小,所以,我们出窗口的条件就跟前面题目不一样,这里只需要count==p.size() 就说明有效字符已经达到,加入left就ok,然后就可以进行出窗口。

所以还是比较简单的,复杂一点的位置和细节,就是要判断有效字符的个数count

进窗口:if(++hash2[s[right]]<=hash1[s[right]]) count++;
//分为两步,先是hash2[s[right]]++,然后判断是不是小于等于hash1[s[right]],是就说明,是一个有效字符
出窗口: if(right-left+1>n&&hash2[s[left]]--<=hash1[s[left++]]) count--;
判断减掉的这个 hash2[s[left]]--,是不是小于等于hash1[s[right]],是就说明,是一个有效字符
更新left: if(count==n) ret.push_back(left);
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int m=s.size(),n=p.size();
        unordered_map<char,int> hash1; for(auto e : p) hash1[e]++; 
        unordered_map<char,int> hash2;
        int count=0;
        vector<int> ret;
        for(int left=0,right=0;right<m;right++)
        {
            //进窗口
            if(++hash2[s[right]]<=hash1[s[right]]) count++;
            //出窗口
            if(right-left+1>n&&hash2[s[left]]--<=hash1[s[left++]]) count--;
            //更新left
            if(count==n) ret.push_back(left);
        }
        return ret;
    }
};

总结:

1.这题很难,首先就是考虑暴力求解问题,对于字符串s里面找p的异位词,就是分别设置两个指针,left和right,从最左边开始找,每次right-left+1==p.size()的时候就判断这个字串是不是跟p的异位词相同,可以考虑把他门都存在两个hash数组中,进行判断,字符出现的次数是不是完全相同,这是暴力,每次当righe-left+1==p.size() 的时候,那么当right++,这个时候left就也要++,每次就重新判断right从left位置开始,重新把数组清空,在重复上面步骤进行比较。

2.就是对于right-left+1==p.size()的时候,进行比较,这个时候不用right回退,只需要left++,right++,也不用清空hash2表,只需要删除hash2[s[left++]]--,并且hash2[[right]]++,让left指针前进就行,相当于增加一个,删除一个字符,这就是典型的滑动窗口,left和right都不会回退,一直向前 ,并且满足进窗口,当right-left+1>m 的时候让left++,在出窗口即可。可是这样时间复杂度还是很高

3.那么就考虑用count计数器来记录有效字符的个数,每次只记录有效字符的个数,当 if(++hash2[s[right]]<=hash1[s[right]]) count++;
,减也是,只有减去的是有效字符,count才--
 if(right-left+1>m) if(hash2[s[left]]--<=hash1[s[left++]]) count--;
并且只在right-left+1>m 超过这个范围的时候,才发生,那么每次只需要判断,有效字符个数达到p.size()的时候才就可以增加数据

7.串联所有单词的⼦串(hard)

这一题,简直就是跟上一题找异位词,百分之99都一样,所以一定要弄懂上一题。

题目意思任然是要在words中的每个单词都以任意顺序组合,然后再s里面找到这种组合的字串,返回所有满足条件的下标。

解析:

1).暴力

就是设置两个指针,left 和 right 然后往后判断,left 到 right 这个区间的字符串是不是能够等于words组合成的顺序,显然,这是绝对不可取的,要进行优化。

2).优化:

小demo,看到这种任意组合的字符串,我们不可能无序的组合,然后在s里面找字串去进行对比,这肯定不可取,那么我们就要跟上题大优化一样,设置计数器count 来记录有效字符串的个数,这样就可以很方便的进行记录。

可以看出,我们只需要讲words里面的字符串设置成a,b这种字符,因为words里面单词的长度都是相等的,那么设置int m=words[0].size() 来表示s里面假设的一个字符的长度,就比如string str=s.substr(left,m);就将str比作是一个字符。那么每次right+=m.left+=m 实际上每次都是跳过m个字符,然后也是设计一个count来计数,当这个str是有效字符的时候count++,设置_str,满足有效字符的时候就left+=m,出窗口。具体更上一题一模一样。

进窗口:string str = s.substr(right, len);
                //进窗口
                hash2[str]++;
                if(hash1.count(str) && hash2[str] <= hash1[str]) count++; //记录有效字符串的个数
出窗口:if(right - left + len > m * len) 
                {
                    string _str = s.substr(left, len);
                    if(hash1.count(_str) && hash2[_str] <= hash1[_str]) count--;
                    hash2[_str]--;
                    left += len;
                }
更新left:if(count == m) ret.push_back(left);
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        int n = s.size(), m = words.size(), len = words[0].size();
        unordered_map<string, int> hash1; for(auto& e : words) hash1[e]++;
        vector<int> ret;
        for(int i = 0; i < len; i++)
        {
            unordered_map<string, int> hash2;
            for(int left = i, right = i, count = 0; right + len <= n; right += len)
            {
                string str = s.substr(right, len);
                //进窗口
                hash2[str]++;
                if(hash1.count(str) && hash2[str] <= hash1[str]) count++;
                //出窗口  固定的滑动窗口
                if(right - left + len > m * len) 
                {
                    string _str = s.substr(left, len);
                    if(hash1.count(_str) && hash2[_str] <= hash1[_str]) count--;
                    hash2[_str]--;
                    left += len;
                }
                if(count == m) ret.push_back(left);
            }
        }
        return ret;
    }
};

总结:

跟上一题固定滑动窗口大小简直一模一样,不一样的就是上一题是滑动字符大小,这次是滑动字符串大小;

1.任然是设置两个hash表进行存入字符串,用count来记录有效字符串的个数

2.区别就是在进窗口的时候要设置str=s.substr(right,m);
在出窗口的时候设置_str=s.substr(left,m);

3.优化,就是要对hash1里面是否存在str 和 _str 进行判断,这样可以减少查询时间

8.最⼩覆盖⼦串(hard)

经过上面那么多题的磨练,这题是我为数不多的自信题,调试了两个小时第一次自己解决一道困难题,其实相对来说还是比较简单的。

题目意思就是找到最小字串,要涵盖t中的全部字符,可以重复包含。

解析:

1).暴力:

任何优化都是通过暴力推导的,所以暴力真的很重要。

暴力就是无脑遍历s,找到字符串长度要大于等于t.size() 然后进行比较,如果包含全部t的字符,就记录长度len,直到求出最短长度。肯定会超时,而且暴力不好实现,所以还是要优化。

2).优化:

那么还是双指针问题,只要right++,判断有一个有效字符,那么count++,直到包含所有的字符,就开始更新len,但是!!!有很多细节问题,比如:

1).在len 比 s.size() 还要长,明显就是没有进入该条件判断,只能返回空串

2).在执行一次后 可能存在 s="bba" t="ab" 这种情况,那么必须要保证 left继续++,所以这个判断应该在while里面判断才行,这样count 的种类数没有减少,说明字串任然可以减少,

3.存在s="aa" t="aa" 说明 可能会加入一个a count就满足种类达标了,那么并不能就这样返回值,所以还要判断只有在len>t.size()的时候才可以采取,否则就未达标。

进窗口:
      if (hash1.count(s[right]) && ++hash2[s[right]] == hash1[s[right]]) count++;
出窗口: while (len>m&&count == hash1.size())
        {
            if (len>m&&len > right - left + 1)
            {
                len = right - left + 1;
                _left = left;
            }
            if (hash1.count(s[left]) && --hash2[s[left]] < hash1[s[left++]]) count--;
            while (left < n && hash2[s[left]] == 0) left++;//跳过非种类字符
        }
更新len:if(len>n) return "";
    return s.substr(_left, len);
class Solution {
public:
    string minWindow(string s, string t) {
    int n = s.size(), m = t.size();
    if (n < m) return "";
    unordered_map<char, int> hash1; for (auto e : t) hash1[e]++;
    unordered_map<char, int> hash2;
    int _left = 0, len = INT_MAX;
    int left = 0, right = 0;
    while (right < n && hash1.count(s[right]) == 0) right++, left++;
    if (left >= n || right >= n) return "";
    int count = 0;//记录种类
    for (right; right < n; right++)
    {
        //进窗口
        if (hash1.count(s[right]) && ++hash2[s[right]] == hash1[s[right]]) count++;
        //出窗口
        while (len>m&&count == hash1.size())
        {
            if (len>m&&len > right - left + 1)
            {
                len = right - left + 1;
                _left = left;
            }
            if (hash1.count(s[left]) && --hash2[s[left]] < hash1[s[left++]]) count--;
            while (left < n && hash2[s[left]] == 0) left++;//跳过非种类字符
        }
    }
    if(len>n) return "";
    return s.substr(_left, len);
}
};

因子都是成对出现的,那么我们就只需要在for里面判断 x*x < n  那么就判断是否有一个m%x==0 那么当x>k 就满足,另一个因子就是 m / x > k 就也会满足。

总结一下,“滑动窗口”问题其实也就是模板问题,只要能明白“该问题是同向的双指针问题”,那么就可以往滑动窗口靠,就是“查找子数组”,就分三大点:进窗口_出窗口_更新len!!!

我觉得我自己的进步挺大的,希望对你也有帮助哦!~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2116657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

arduino ide安装详细步骤

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; Arduino IDE 是一个专为编程 Arduino 微控制器设计的集成开发环境&#xff0c;使用起来非常方便。下面将介绍如何在不同平台上安装 Arduino IDE 的详细步骤&#xff0c;包括 Windows、Mac 和 Linux 系统。 …

计算机毕业设计选题推荐-乐器推荐系统-乐器商城-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 11部署BGP

本章应有助于回答以下问题: 核心的配置概念是什么?如何为 Clos 网络配置 BGP ?无编号的 BGP 如何工作?如何配置 BGP 与主机上的 BGP 发言者 (例如 Kube-router) 建立对等关系?如何配置 BGP 以对网络进行计划维护? 核心的 BGP 配置概念 全局BGP 配置&#xff0c;包含: r…

了解开源消息代理RabbitMQ

1.RabbitMQ 是什么&#xff1f; RabbitMQ是一个消息代理:它接受并转发消息。你可以把它想象成邮局:当你把要寄的邮件放进邮箱时&#xff0c;你可以确定邮递员最终会把邮件送到收件人那里。在这个比喻中&#xff0c;RabbitMQ是一个邮筒、一个邮局和一个邮递员。RabbitMQ和邮局之…

JavaScript 循环分支语句-for循环

先n1,判断n是否<10,满足条件&#xff0c;n1&#xff0c;输出n&#xff0c;再次判断n是否<10&#xff0c;循环.......... <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Comp…

SQL进阶技巧:截止当前批次前的批次量与订单量 | 移动窗口问题

目录 0 场景描述 1 数据准备 2 问题分析 3 小结 0 场景描述 表A有如下字段,user id(用户ID),batch id(批次ID),order id(订单ID),create time(创建时间),同一个用户ID下有多个批次,同一个批次下有多个订单ID,相同批次ID的创建时间是相同的,创建时间精确到了秒。 统计,截…

如何通过ip命令修改网卡名?(ip link set en1p0f0 name eth0)

快速提取 ip link set enp1s0f0 down ip link set enp1s0f0 name eth0 ip link set enp1s0f0 up ifconfig eth0背景 ifconfig命令不支持修改网卡名字。可以使用ip link进行修改。 从Linux 4.9版本开始&#xff0c;ip link 命令支持一个 set 子命令&#xff0c;可以用来修改接…

iPhone16全系采用A18处理器,不再区别对待,市场压力所致

iPhone16即将在3天后发布&#xff0c;外媒基本确定今年的iPhone16将全系采用A18处理器&#xff0c;与此前的基本款采用上一代处理器有很大的差异&#xff0c;这对于苹果用户来说无疑是一大利好&#xff0c;也将有助于推动iPhone16的销售。 此前的iPhone14和iPhone15&#xff0c…

[Redis] Redis基本命令与数据类型+单线程模型

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

【springboot】使用AOP

目录 1. 添加依赖2. 创建切面类1. 创建切面类2. 切点表达式3. 增强方法 3. 开启AOP4. 创建控制类5. 测试 1. 添加依赖 <!-- AOP依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop<…

【iOS】MVC入门

【iOS】MVC模式的学习 文章目录 【iOS】MVC模式的学习前言MVC模式概念MVC的交流模式MVC的一个简单实践Model层View层Controller层 MVC的优点与缺点总结 前言 笔者在暑假的学习中完成了一些小项目&#xff0c;这些小项目中间有时候出现了一个小bug都要寻找很久&#xff0c;而且会…

前端学习笔记-Web APls篇-05

Bom操作 1.Window对象 1.1BOM(浏览器对象模型&#xff09; BOM(Browser Object Model ) 是浏览器对象模型 window对象是一个全局对象&#xff0c;也可以说是JavaScript中的顶级对象像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方法都…

SAM2POINT:以zero-shot且快速的方式将任何 3D 视频分割为视频

摘要 我们介绍 SAM2POINT&#xff0c;这是一种采用 Segment Anything Model 2 (SAM 2) 进行零样本和快速 3D 分割的初步探索。 SAM2POINT 将任何 3D 数据解释为一系列多向视频&#xff0c;并利用 SAM 2 进行 3D 空间分割&#xff0c;无需进一步训练或 2D-3D 投影。 我们的框架…

ML19_GMM高斯混合模型详解

1. 中心极限定理 中心极限定理&#xff08;Central Limit Theorem, CLT&#xff09;是概率论中的一个重要定理&#xff0c;它描述了在一定条件下&#xff0c;独立同分布的随机变量序列的标准化和的分布趋向于正态分布的性质。这个定理在统计学中有着广泛的应用&#xff0c;尤其…

算法篇_C语言实现霍夫曼编码算法

一、前言 霍夫曼编码&#xff08;Huffman Coding&#xff09;是一种广泛使用的数据压缩算法&#xff0c;特别适用于无损数据压缩。它是由David A. Huffman在1952年提出的&#xff0c;并且通常用于文件压缩和传输中减少数据量。霍夫曼编码的核心思想是使用变长编码表对源数据进…

提升效率必备!学习awk命令,轻松搞定数据

在日常的工作中&#xff0c;无论是数据处理、日志分析&#xff0c;还是格式化输出&#xff0c;AWK命令都是不可或缺的利器。AWK是一种强大的文本处理工具&#xff0c;能让你轻松处理复杂的数据&#xff0c;提升工作效率。本文将为你介绍AWK的基本功能及一些实用场景&#xff0c…

树 --- 二叉树

树的物理结构和逻辑结构上都是树形结构。 树形结构&#xff1a;由一个根和若干个子节点组成的集合。 最外围的为叶子节点&#xff1a;只有前驱而没有后继。 &#xff08;一&#xff09;树的性质 • ⼦树是不相交的 • 除了根结点外&#xff0c;每个结点有且仅有⼀个⽗结点 •…

每天五分钟玩转深度学习框架PyTorch:将nn的神经网络层连接起来

本文重点 前面我们学习pytorch中已经封装好的神经网络层,有全连接层,激活层,卷积层等等,我们可以直接使用。 如代码所示我们直接使用了两个nn.Linear(),这两个linear之间并没有组合在一起,所以forward的之后,分别调用了,在实际使用中我们常常将几个神经层组合在一起…

【EI会议征稿通知】第十一届机械工程、材料和自动化技术国际会议(MMEAT 2025)

第十一届机械工程、材料和自动化技术国际会议&#xff08;MMEAT 2025&#xff09; 2025 11th International Conference on Mechanical Engineering, Materials and Automation Technology 本次大会旨在汇聚全球机械工程、材料科学及自动化技术的创新学者和行业专家&#xff0…

使用 BentoML快速实现Llama-3推理服务

介绍 近年来&#xff0c;开源大模型如雨后春笋般涌现&#xff0c;为自然语言处理领域带来了革命性的变化。从文本生成到代码编写&#xff0c;从机器翻译到问答系统&#xff0c;开源大模型展现出惊人的能力&#xff0c;吸引了越来越多的开发者和企业投身其中。 然而&#xff0…