目录
原理:定义两个指针(下标)来维护所指向的区间始终是符合题目要求,大致分为三步:
1进窗口:用一个指针来进行遍历搜索使指针区间符合要求
2更新值:该区间符合要求后记录存储数值(可以是任意区间)
3出窗口:另一个指针开始进行向后走继续查找符合要求的区间
一长度最小的子数组
oj链接:长度最小的子数组
思路:定义left与right两个下标进行区间范围的查找用sum记录left与right区间之和,从中找出最小的那一段子数组:
进窗口:right移动进行sum+=
出窗口:相加的和>target,left向后移动进行sum-=直到区间之和再次符合要求(循环)
在出窗口之前更新数据从而在多次区间中找到最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int ret=INT_MAX,sum=0;
for(int left=0,right=0;right<nums.size();right++)
{
sum+=nums[right];
while(sum>=target)
{
//left与right之间的数符合要求就记录下来
//取最小的数
ret=min(ret,right-left+1);
sum-=nums[left];
left++;
}
}
//ret没有变化说明不存在有连续子数组>=target
return ret==INT_MAX?0:ret;
}
};
二无重复字符的最长子串
oj链接:无重复字符的最长子串
思路:与上面思路类似:使用滑动窗口但要用一个数组来存储字符的个数,个数>1说明此时right指向的字符是重复字符要进行出窗口的动作;
最后在更新子串的数据找到最长子串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//模拟哈希表存储字符个数
int hash[128]={0},ret=INT_MIN;
for(int left=0,right=0;right<s.size();right++)
{
//进窗口
hash[s[right]]++;
//说明right指向的字符已经出现重复
while(hash[s[right]]>1)
{
//出窗口
hash[s[left]]--;
left++;
}
//更新数据
ret=max(ret,right-left+1);
}
return ret==INT_MIN? 0 : ret;
}
};
三最大连续1的个数Ⅲ
oj链接:最大连续1的个数 III
思路:用计数器count来记录0的个数+滑动窗口思想进行解答即可:
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
//用count记录0出现的个数
int count=0,ret=INT_MIN;
for(int left=0,right=0;right<nums.size();right++)
{
//进窗口
if(nums[right]==0)
{
count++;
}
//right位置的0超过规定的最大翻转0的个数
//进行出窗口操作
while(count>k)
{
if(nums[left]==0)
{
count--;
}
left++;
}
//更新数据
ret=max(ret,right-left+1);
}
return ret==INT_MIN? 0 : ret;
}
};
四将x减到0的最小操作数
oj链接:将 x 减到 0 的最小操作数
思路:题中要想求出最小的数组个数相加=x还规定值得在左右两端进行选择,实现起来非常困难。因此,
我们将问题转化为(滑动窗口)求最长的数组个数相加=数组之和-x
剩余的数组个数不就是题目要求的吗?
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
//正难反易
int sum=0,len=INT_MIN,sum1=0;
//求出数组之和
for(auto ch : nums)
{
sum+=ch;
}
//和减去x即是我们说要求的目标值
int target=sum-x;
//即不可能找到最长数组长度(都是正整数)
if(target<0)
{
return -1;
}
//求最小操作数转化为求出最长数组长度=target
for(int left=0,right=0;right<nums.size();right++)
{
sum1+=nums[right];
while(sum1>target)
{
sum1-=nums[left];
left++;
}
if(sum1==target)
{
len=max(len,right-left+1);
}
}
//如果没找到返回-1,找到返回总长-len
return len==INT_MIN?-1:nums.size()-len;
}
};
五水果成篮
oj链接:水果成篮
思路:哈希表+计数器+滑动窗口
用数组模拟哈希表来存储数值,用count记录水果种类:
进窗口:right进行遍历存储数值与种类的记录
出窗口:种类超过2种,left进行向右移动直到种类在2种一下(循环)
更新结果记录left与right的水果个数,找到最多的那个即为被题答案!
class Solution {
public:
int totalFruit(vector<int>& fruits) {
//计数器count与数组存储值
int count=0,ret=INT_MIN,hash[100001]={0};
for(int left=0,right=0;right<fruits.size();right++)
{
if(hash[fruits[right]]==0)
{
count++;
}
hash[fruits[right]]++;
while(count>2)
{
hash[fruits[left]]--;
if(hash[fruits[left]]==0)
{
count--;
}
left++;
}
ret=max(ret,right-left+1);
}
return ret==INT_MIN? 0:ret;
}
};
六找到字符串中所有字母异位词
oj链接:找到字符串中所有字母异位词
思路:先用数组模拟哈希表统计p中的字符,创建计数器count来统计‘有效字符的个数’;
用滑动窗口的思想:定义left与right,right向后走的同时,也用哈希表来统计字符,当字符个数<= p字符的个数即为有效字符,count++;
让left与right的区间始终保持在len(p字符的个数)个。当count=len时这段区间就是我们要找的字母异位词!
进窗口:right走的同时统计走过的字符个数
出窗口:只要left与right之间的个数>len就进行left出窗口的操作(因为是区间是固定所以仅需判断即可)
更新数值:cout==len这段窗口就是我们的答案,记录left的下标。
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
//count统计“有效字符的个数”即
int hash1[128]={0},hash2[128]={0},count=0,len=p.size();
//hash1统计p字符
for(auto ch : p)
{
hash1[ch]++;
}
//hash2用来统计left与right之间的字符
for(int left=0,right=0;right<s.size();right++)
{
hash2[s[right]]++;
//hash2[in]<=hash1[in]说明right指向的字符是有效字符
if(hash2[s[right]]<=hash1[s[right]])
{
count++;
}
//left与right区间超过len值进行left向右移动一次
if(right-left+1>len)
{
//left要移动之前先检查left指向的字符是不是有效字符
if(hash2[s[left]]<=hash1[s[left]])
{
count--;
}
hash2[s[left]]--;
left++;
}
//left与right之间的字符符合p的条件
if(count==len)
{
ans.push_back(left);
}
}
return ans;
}
};
七串联所有单词的子串
oj链接:串联所有单词的子串
与上一道题思路类似,只不过是把字符变成字符串而已,自己动手尝试吧!
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
//用哈希表存储单词
unordered_map<string,int> hash;
vector<int> ans;
for(auto& ch:words)
{
hash[ch]++;
}
int n=words[0].size();
for(int i=0;i<n;i++)
{
unordered_map<string,int> hash1;
for(int left=i,right=i,count=0;right+n<=s.size();right+=n)
{
string in=s.substr(right,n);
hash1[in]++;
if(hash1[in]<=hash[in])
{
count++;
}
if(right-left+1>words.size()*n)
{
string out=s.substr(left,n);
if(hash1[out]<=hash[out])
{
count--;
}
hash1[out]--;
left+=n;
}
if(count==words.size())
{
ans.push_back(left);
}
}
}
return ans;
}
};
八最小覆盖子串
oj链接:最小覆盖子串
思路:先用数组统计p中字符,用kind记录p中字符的种类。(与上面的不同)
滑动窗口:
定义两个‘指针’left与right
进窗口:
用right进行向右搜索,再用数组统计right走过的字符,
用count记录right走过的字符中是属性p的字符种类
(循环)判断:count==kind说明left与right的区间符合条件
更新结果,记录符合条件中最短的子串与left的位置
出窗口,(如果left当前指向的字符刚好是p中字符个数,count--)left向右走(跳出循环)
class Solution {
public:
string minWindow(string s, string t) {
//用kind来记录t字符串中的种类,hash1统计t中字符串的字符
int kind=0,hash1[128]={ 0 };
for(auto ch : t)
{
if(hash1[ch]==0)
{
kind++;
}
hash1[ch]++;
}
//hash2统计s中字符个数,count统计right维护区间内字符是t中字符种类相同的值
//pos记录最短字符串的起始位置
int hash2[128]={ 0 },count=0,minlen=INT_MAX,pos=-1;
for(int left=0,right=0,count=0;right<s.size();right++)
{
//进窗口
int ch=s[right];
hash2[ch]++;
//加完相等说明是t中字符的种类
if(hash2[ch]==hash1[ch])
{
count++;
}
while(count==kind)
{
//更新结果
if(right-left+1<minlen)
{
minlen=right-left+1;
pos=left;
}
int out=s[left];
if(hash2[out]==hash1[out])
{
count--;
}
//出窗口
hash2[out]--;
left++;
}
}
//找不到最短字符
if(pos==-1)
{
return "";
}
return s.substr(pos,minlen);
}
};