核心思想:
-
滑动窗口的核心思想是使用两个指针(或索引)来标记窗口的左右边界,并动态调整窗口的大小和位置,以在一次遍历中解决问题。窗口在遍历过程中“滑动”或者“移动”,使得算法能够在数据结构中找到所需的子序列,而无需反复遍历相同的子序列。
处理思路:
-
通常,窗口的初始大小为 0,即两个指针都指向数据结构的起始位置。
-
右指针向右移动,扩展窗口的右边界,包含新的元素。
-
在每次右指针移动后,检查当前窗口是否满足特定条件(如无重复字符、窗口内的元素和满足某个值等)。如果满足条件,可以记录或更新结果。
-
如果窗口不再满足条件,则通过移动左指针缩小窗口的左边界,从而移除掉一些元素。这一步用于调整窗口的大小,使其重新满足条件。
-
右指针继续移动,重复上述步骤,直到遍历完所有元素。
适用场景:
-
找到长度固定或可变的最大/最小子数组(或子串)。
-
计算具有特定属性的子数组(如和、乘积等)。
-
查找满足特定条件的最长/最短子数组(或子串)。
案例解析:
力扣num-3:
题目描述:给定一个字符串string,要求找出不含重复元素的最长字串的长度。
-
字符串“ abcdeafg”,这个字符串的最长不重复子串为“ abcde ”。
-
使用滑动窗口的思想,left 和 right 指针初始都指向字符串的开头。right 用于扩展窗口,left 用于收缩窗口以排除重复字符。
-
右指针 right 在每次循环中向右移动,并将当前字符加入哈希表,记录其出现的次数。在右指针移动之后,检查当前字符在哈希表中的次数。如果该字符的次数大于 1,说明在当前窗口中有重复字符。
-
当发现重复字符时,开始通过移动左指针 left 缩小窗口,同时将 left 指向的字符从哈希表中移除。左指针继续移动,直到窗口内不再包含重复字符为止。
-
例如,对于字符串 abcdeafg,当右指针移动到第二个 a 时,发现 a 已经存在于哈希表中,因此需要移动左指针,直到移除掉第一个 a。
-
每次调整完左指针并且窗口内无重复字符后,计算当前窗口的长度(right - left),并与 ret 比较,更新 ret 为较大值。
-
右指针继续向右移动,重复上述过程,直到遍历完字符串的所有字符。
class Solution {
public:
int lengthOfLongestSubstring(string s)
{
int tmphash[128]={0}; //记录每个字符出现次数的哈希表。
int left=0; //左指针
int right=0; //右指针
int n=s.size(); //字符串长度
int ret=0; //最长不重复子串长度
while(right<n) //当右指针不超出字符串区间,持续循环
{
tmphash[s[right]]++; //将右指针指向的元素在哈希表中的数量加一
while(tmphash[s[right]]>1) //如果元素出现在哈希表的数量大于一,说明重复出现
{
tmphash[s[left++]]--; //出现重复元素,开始调整左指针,从左指针位置开始向后,直到找到重复元素,注意找到后left还会++,所以left会指向重复元素的后一个元素的位置。
}
right++; //使用的是while循环,所以要手动调整right指针的位置
ret=max(ret,right-left); //更新子串最大长度
}
return ret; //返回子串最大长度
}
};
力扣num-1004:
题目描述:给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
-
比如数组为:[1,1,1,0,0,0,1,1,1,1,0],且k的值为2。那么翻转下标为4和5的两个0,最长的连续1的数量为6。或者翻转下标为5和10的两个0,最长的数量也是6。
-
这个题可以翻译为找到最长的连续0。所以可以使用滑动窗口解题。
class Solution {
public:
int longestOnes(vector<int>& nums, int k)
{
int left=0;
int right=0;
int num=0;
int ret=0;
while(right<nums.size()) //循环
{
if(nums[right]==0) num++; //如果right下标为0,就将0的计数器+1
while(k<num) //如果0的数量超过k,挪动left,将0的计数器维持在不大于k,也就是小于等于k。
{
if(nums[left++]==0) num--;
}
right++; //while循环,需要手动挪动right指针
ret=max(ret,right-left); //计算当前最大连续1的数量
}
return ret;
}
};
力扣num-1658:
题目描述:给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
-
理解“ 正难则反 ”的理念。这个题正面想比较难解,就反向思考。
-
将题目转换为在数组中找到一个最大子数组,使得这个数组的和等于sum(nums) - x 。这样变相就能够得到左右两边要删去哪些数。
class Solution {
public:
int minOperations(vector<int>& nums, int x)
{
int sum=accumulate(nums.begin(),nums.end(),0); //计算vector的和
int target=sum-x; //计算目标数组的和
int n=nums.size(); //数组大小
if(target<0)
return -1;
if(target==0) //处理tmp==sum的情况
return n;
int left=0;
int right=0;
int tmp=0;
int ret=-1;
for(;right<n;right++)
{
tmp+=nums[right];
while(tmp>target)
{
tmp-=nums[left++];
}
if(tmp==target)
{
ret=max(ret,right-left+1); //更新长度
}
}
if(ret>0) return n-ret;
return ret;
}
};
力扣num-904:
题目描述:在数组中找到一个连续区域,使得这个连续区域只包含两中元素,且这两个元素在数组中占比最大。比如{1,2,2,3,2,3,3,4}中最大的连续数组就是{2,2,3,2,3,3}。在比如{1,2,3,4,4,5,5,6}中就是找到{4,4,5,5}。
-
本质上就是找最长子数组。要求是数组中最多不超过2种(1种或2种)元素。
-
通过unordered_map创建哈希表,使得既能够存储元素的种类,又能够存储元素的数量。
-
挪动右指针向哈希表添加元素,每次添加后都更新最长子数组的长度。
-
当哈希表中的元素大于两种就挪动左指针。同时对哈希表中的元素的数量做减少,如果某个元素的数量减少到0,删除这个元素,此时哈希表的元素数量再次被维护到两种。
-
直到右指针遍历完整个数组,返回最长需求子数组的长度即可。
class Solution {
public:
int totalFruit(vector<int>& fruits)
{
unordered_map<int,int> hash; //创建哈希表
int left=0;
int right=0;
int ret=0;
while(right<fruits.size())
{
hash[fruits[right]]++; //哈希表入元素
while(hash.size()>2) //哈希表元素维持在2
{
hash[fruits[left]]--; //超过两种,开始挪动左指针直到删去一种
if(hash[fruits[left]]==0)
{
hash.erase(fruits[left]);
}
left++;
}
right++;
ret=max(ret,right-left); //更新最大长度。
}
return ret;
}
};
力扣num-438:
题目描述:给定两个字符串a和b,找到a中所有b的异位词子串。返回子串的索引,不考虑返回的顺序。比如abab中找ba,就返回{0,1,2}。
-
首先思考如何比较两个字符串是否为异位字符串?将两个字符串都对丢到哈希表中,进行比较。
-
暴力解法:在字符串a中,依次找到长度为字符串b长度的子字符串,比较两个字符串是否相等,相等则在一个vector中记录这个子串的起始下标。最后返回这个vector。
-
优化解法:比如有一个字符串 “abcdacbefg”,找到所有 “abc”的异位子字符串,找到第一个异位子字符串下标为0,此时无需清空哈希表,而是直接将下标0位置字符出哈希表,将下标3字符入哈希表,再次比较即可。
class Solution {
public:
vector<int> findAnagrams(string s, string p)
{
char hash1[26]={0}; //创建哈希表1,存储p字符串
for(auto a: p) hash1[a -'a']++;
vector<int> ret; //返回数组
if(p.size()>s.size()) return ret; //如果p的长度大于s,表示s没有子串和p等长,直接返回即可
int left=0;
int right=0;
char hash2[26]={0}; //哈希表2
for(;right<p.size()-1;right++) hash2[s[right]-'a']++;
while(right<s.size())
{
hash2[s[right]-'a']++;//入窗口
if(right-left+1>p.size()) //窗口大小大于p串大小,出窗口
{
hash2[s[left]-'a']--;
left++;
}
int flag=1;
for(int i=0;i<26;i++) //比较两个串是否相同,根据哈希表存储的串中每个字符的数量
{
if(hash1[i]!=hash2[i])
{
flag=0;
}
}
if(flag==1) //如果串相同,则尾插left位置到返回值数组
{
ret.push_back(left);
}
right++;
}
return ret;
}
};