滑动窗口or双指针
长度最小的子数组(子数组的和>=target且长度最小
滑动窗口做法O(n)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int l=0;
int len=0x3f3f3f3f;
int sum=0;
for(int r=0;r<nums.size();r++){
sum+=nums[r];
while(sum>=target){
len=min(len,r-l+1);//此时已经是满足的,不能放过,去继续缩小区间
sum-=nums[l];
l++;
// len=min(len,r-l+1);
}
}
return len==0x3f3f3f3f?0:len;
}
};
看错题惨过打输架,是≥ target
而不是 ==target
,脂肪压了眼睛?
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int l=0;
int len=1e5+5;
int sum=0;
for(int r=0;r<nums.size();r++){
sum+=nums[r];
while(sum>target){
sum-=nums[l];
l++;
}
else if(sum==target){
len=min(len,r-l+1);
}
}
return len==1e5+5?0:len;
}
};
二分查找做法O(nlogn)
判断的是以i为左端点的区间,由于遍历前缀和数组搜索满足条件的右端点
若在nums
数组中[l,r]
区间元素的和为target
,
那么在sums数组中区间右端点sums[r]
即为sums[l-1]+target
又 前缀和数组一定是升序(或非降序)的,故可以二分搜索右端点
class Solution {
public:
int search(vector<int>& sums,int l,int r,int target){
while(l<r){//找到第一个大于等于target的值
int mid=l+r>>1;
if(sums[mid]<target){
l=mid+1;
}
else r=mid;
}
if(sums[l]>=target)return l;
else return -1;//没找到
}
int minSubArrayLen(int target, vector<int>& nums) {
// for(int i=1;i<nums.size();i++){
// nums[i]=nums[i-1]+nums[i];
// }//前缀和数组 下标从1开始的好!!!
int n=nums.size();
int[] sums = new int[n + 1];
// 为了方便计算,令 size = n + 1
// sums[0] = 0 意味着前 0 个元素的前缀和为 0
// sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
// 以此类推
for (int i = 1; i <= n; ++i){
sums[i] = sums[i - 1] + nums[i - 1];
}//前缀和数组 下标从1开始的好
int l=0;
int len=0x3f3f3f3f;
for(int i=1;i<=n;i++){//前缀和数组一定是升序(或非降序)的
int tag=target+sums[i-1];
//判断的是以i为左端点的区间,由于遍历前缀和数组搜索满足条件的右端点
// 若在nums数组中[l,r]区间元素的和为target,
// 那么在sums数组中区间右端点sums[r]即为sums[l-1]+target
int r=search(sums,i,n,tag);
if(r!=-1){
len=min(len,r-i+1);
}
}
return len==0x3f3f3f3f?0:len;
}
};
字符串的排列(str1的某个排列是str2的子串【哈希表+双指针】)
class Solution {
public:
bool checkInclusion(string s1, string s2) {
// int cnt[30];在leetcode不要这样声明数组,否则必须有下面的初始化这句
// memset(cnt,0,sizeof(cnt));
vector<int>cnt(26,0);
for(int i=0;i<s1.size();i++){
cnt[s1[i]-'a']++;
}//用累计字母出现次数的方式 确定一个字符串的字母组成
int n=s2.size();
int l=0;
for(int r=0;r<n;r++){
cnt[s2[r]-'a']--;
while(cnt[s2[r]-'a']<0){//[l,r]区间中出现的s[r]次数多于s1字符串
cnt[s2[l]-'a']++;//缩小区间,l向右移,直到将多余的s[r]摒弃在区间外
l++;
}
if(r-l+1==s1.size()){
return true;
}//[l,r]区间长度相等了,而且前提是没有多出 某个字母
// 不会出现s1中没有的字母,否则cnt[r]会小于0
// 全都是s1中的字母,且对应字母出现的次数没有超出,长度又保证了字母不会少
}
return false;
}
};
或者直接比较两个 哈希表数组
class Solution {
public:
bool checkInclusion(string s1, string s2) {
// 排除异常的边界情况,也限定了模式串的长度
if(s1.size() > s2.size()) return false;
// 匹配采用的窗口大小为模式串大小
int windowSize = s1.size();
// 模式串的字典:可以看做一种频率分布
vector<int> hashmap1(26, 0);
// 动态更新的匹配窗口字典
vector<int> hashmap2(26, 0);
// 构建字典
for(int i = 0; i < windowSize; i++) {
hashmap1[s1[i] - 'a']++;
hashmap2[s2[i] - 'a']++;
}
// 对于每一轮滑窗查询,如果两个字典相等(频率分布一致),则命中
for(int i = windowSize; i < s2.size(); i++) {
// 两个字典相等(频率分布一致),则命中
if(hashmap1 == hashmap2) return true;
// 否则,向右滑窗:滑窗对于 hash 表的操作变为对应频率的增减
hashmap2[s2[i - windowSize] - 'a']--;
hashmap2[s2[i] - 'a']++;
}
// 整个算法采用左闭右开区间,因此最后还有一个窗口没有判断
return hashmap1 == hashmap2;
}
};
239.滑动窗口最大值
30.串联所有单词的子串
76.最小覆盖子串