参考资料来源灵神在力扣所发的题单,仅供分享学习笔记和记录,无商业用途。
原理:在滑动窗口的理解基础上根据条件来调整滑动窗口区间来获取正确答案。
核心思路:维护一个动态窗口,通过调整窗口边界来满足特定条件,同时更新最优解。
模板:
for(int i=0;i<nums.size();i++){
//插入数据
while(buff>=target){ //满足条件
//更新结果
//剔除窗口头节点
}
}
补充:在满足条件的过程中进行缩小窗口就是在获取最小答案的过程中直到缩小的结果打破条件
力扣题单练习(灵神题单中摘取题目)
题意:给定一组数据和一个目标值,找到长度最小子数组之和大于等于目标值
思路:采用不定长滑动窗口在满足条件时候边更新答案,边更新窗口头节点这样即可获取最短满足条件子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//题意:给定一组数据和一个目标值,找到长度最小子数组之和大于等于目标值
//思路:采用不定长滑动窗口在满足条件时候边更新答案,边更新窗口头节点这样即可获取最短满足条件子数组
if(reduce(nums.begin(),nums.end())<target) return 0; //边界判断
int ret=INT_MAX,head=0,buff=0,flag=0;
for(int i=0;i<nums.size();i++){
buff+=nums[i]; //插入数据
while(buff>=target){
ret=min(ret,i-head+1); //更新结果
buff-=nums[head++]; //剔除数据
}
}
return ret;
}
};
题意:给定一个二进制字符串,要求子字符串中1的数量满足等于k的最短子字符串
思路:遍历数据记录子字符串1的长度,当长度大于k时候进行缩减窗口并进行更新数据
class Solution {
public:
string check(string ret, string buff){ //判断字典序最小的子字符串
for(int i=0;i<ret.size();i++){
if(ret[i]==buff[i]) continue;
if(ret[i]>buff[i]) return buff;
else return ret;
}
return ret;
}
string shortestBeautifulSubstring(string s, int k) {
//题意:给定一个二进制字符串,要求子字符串中1的数量满足等于k的最短子字符串
//思路:遍历数据记录子字符串1的长度,当长度大于k时候进行缩减窗口并进行更新数据
string ret="",buff="";
int head=0,cnt=0;
for(int i=0;i<s.size();i++){
if(s[i]=='1') cnt++;
buff+=s[i];
if(cnt<k) continue;
while(cnt==k){
if(ret=="") ret=buff;
else if(buff.size()<ret.size()) ret=buff;
else if(buff.size()==ret.size()) ret=check(ret,buff);
if(s[head]=='1') cnt--; //打破条件
head++;
buff=s.substr(head,i-head+1);
}
}
return ret;
}
};
题意:
给定一个只有4个字符的字符串要求这四个字符的长度都为总长/4,让我们替换最短子串满足条件
思路:
遍历判断字符串是否合法,在获取超出长度的字符进行记录子串必要的数据。
在遍历过程中,当窗口内字符满足必要数据则进入窗口缩减并进行更新答案即可。
class Solution {
public:
//判断当前窗口内是否还有必要数据
bool check(unordered_map<char,int>& need, unordered_map<char,int>& win){
for(auto x: need) if(win[x.first]<x.second) return false;
return true;
}
int balancedString(string s) {
//题意:给定一个只有4个字符的字符串要求这四个字符的长度都为总长/4,让我们替换最短子串满足条件
//思路:遍历判断字符串是否合法,在获取超出长度的字符进行记录子串必要的数据。
//在遍历过程中,当窗口内字符满足必要数据则进入窗口缩减并进行更新答案即可。
int k=s.size()/4;
unordered_map<char,int> map;
for(int i=0;i<s.size();i++) map[s[i]]++; //统计字符长度
bool vis=true;
for(char x : {'Q', 'W', 'E', 'R'}){ //判断是否直接满足条件
if(map[x]!=k){
vis=false;
break;
}
}
if(vis) return 0;
unordered_map<char,int> need;
for(char x : {'Q', 'W', 'E', 'R'}) if(map[x]>k) need[x]=map[x]-k; //必要数据
unordered_map<char,int> win; //窗口数据
int ret=INT_MAX,head=0;
for(int i=0;i<s.size();i++){
if(need.find(s[i])!=need.end()) win[s[i]]++; //插入窗口数据
while(check(need,win)){ //满足条件
ret=min(ret,i-head+1);
if(need.find(s[head])!=need.end()) win[s[head]]--; //试图打破条件
head++;
}
}
return ret;
}
};
题意:给定的一组数据可以循环使用,要求找到元素和等于目标值的最短子数组。
思路:目标值可能很大,如果从头到尾循环数据到等于它浪费时间效率。期间有很多组nums重复计算数据。
逆向思维:找目标值%nums元素和的区间,这样只需要循环一次包含所有余下区间即可获取答案。
class Solution {
public:
int minSizeSubarray(vector<int>& nums, int target) {
//题意:给定的一组数据可以循环使用,要求找到元素和等于目标值的最短子数组。
//思路:目标值可能很大,如果从头到尾循环数据到等于它浪费时间效率。期间有很多组nums重复计算数据。
//逆向思维:找目标值%nums元素和的区间,这样只需要循环一次包含所有余下区间即可获取答案。
int head=0,ret=INT_MAX,buff=0,n=nums.size();
long long sum=reduce(nums.begin(),nums.end(),0LL);
for(int i=0;i<nums.size()*2;i++){
buff+=nums[i%n]; //插入数据
while(buff>target%sum){
buff-=nums[head%n]; //删除数据
head++;
}
if(buff==target%sum) ret=min(ret,i-head+1); //记录数据
}
return ret==INT_MAX?-1:ret+target/sum*n;
}
};
题意:要求s包含t所有字符的最小子串
思路:
用哈希表进行统计t字符串记录必要条件。
当满足必要条件采用模板不定长滑动窗口,边更新答案,边试图打破条件(打破那一次就是最短子串)
class Solution {
public:
string minWindow(string s, string t) {
//题意:要求s包含t所有字符的最小子串
//思路:用哈希表进行统计t字符串记录必要条件。
//当满足必要条件采用模板不定长滑动窗口,边更新答案,边试图打破条件(打破那一次就是最短子串)
if(s.size()<t.size()) return "";
//need:必要条件 win:窗口满足条件
unordered_map<char,int> need, win;
//head:窗口大小、min_head:答案最前下标位置,min_size:答案子串长度 cnt:统计是否满足必要条件
int head=0,min_head=0,min_size=INT_MAX,cnt=0;
for(auto x: t) need[x]++;
int buff=need.size(); //必要条件满足数量
for(int i=0;i<s.size();i++){
if(need.find(s[i])!=need.end()){ //统计必要条件满足数量
win[s[i]]++;
if(win[s[i]]==need[s[i]]) cnt++;
}
while(head<=i && cnt==buff){
if(i-head+1<min_size){ //更新结果
min_head=head;
min_size=i-head+1;
}
if(need.find(s[head])!=need.end()){ //试图打破条件
win[s[head]]--;
if(win[s[head]]<need[s[head]]){
cnt--;
}
}
head++;
}
}
return min_size==INT_MAX?"":s.substr(min_head,min_size);
}
};
题意:给定k组数据,要求出最小区间包含k组数据其中至少一位数。也就是求最小包含区间
思路:
把k组数据化成一维数据并且记录对应组,在进行排序。这样我们就获取了最长包含区间
必要条件就是要至少有k个不同组元素。当满足条件时
class Solution {
public:
vector<int> smallestRange(vector<vector<int>>& nums) {
//题意:给定k组数据,要求出最小区间包含k组数据其中至少一位数。也就是求最小包含区间
//思路:把k组数据化成一维数据并且记录对应组,在进行排序。这样我们就获取了最长包含区间
//必要条件就是要至少有k个不同组元素。当满足条件时
vector<pair<int,int>> ans;
for(int i=0;i<nums.size();i++){ //统计数据
for(auto k: nums[i]){
ans.emplace_back(k,i);
}
}
sort(ans.begin(),ans.end()); //排序
vector<int> vis(nums.size());
int ans_head=ans[0].first; //最短包含区间头元素
int ans_tail=ans[ans.size()-1].first; ////最短包含区间尾元素
int cnt=nums.size(),buff=0; //cnt:必要条件满足数量 buff:窗口头
for(auto [a,b]:ans){
if(vis[b]==0) cnt--; //当前组第一次出现的数据,必要条件--
vis[b]++; //当前组标记记录了,无需必要条件--;
while(cnt==0){
if(a-ans[buff].first<ans_tail-ans_head){ //更新结果
ans_tail=a;
ans_head=ans[buff].first;
}
vis[ans[buff].second]--; //删除数据
if(vis[ans[buff].second]==0) cnt++; //破坏必要条件
buff++; //缩小窗口
}
}
return {ans_head,ans_tail};
}
};