本次是定长滑动窗口的最后一篇,会更新灵神题单除了会员题目以外的所有题目
灵神题单:分享丨【题单】滑动窗口与双指针(定长/不定长/至多/至少/恰好/单序列/双序列/三指针) - 力扣(LeetCode)
题目1:1052. 爱生气的书店老板 - 力扣(LeetCode)
我们可以思考一下最后的答案由什么组成?答案有两部分:
1.本来老板就不生气,顾客能满意的数量。记为sum
2.强制minutes分钟不生气,让minutes分钟内本来生气的顾客满意。记为maxS
第一部分答案是固定的,所以我们只需要让第二部分最大,问题就转化为minutes内顾客不满意最多的数量。这就可以应用滑动窗口解决了。本题代码如下:
class Solution {
public:
int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) {
int sum = 0;//记录不生气时的总客户数量
for(int i = 0;i<grumpy.size();i++){
if(!grumpy[i]) sum += customers[i];
}
//长度为minutes的滑窗
int ans = 0;
int s = 0;//维护窗口内生气的客户数量
for(int l = 0,r = 0;r<customers.size();r++){
if(grumpy[r]) s+=customers[r];
if(r < minutes -1) continue;
//更新答案
ans = max(ans,s);
//出窗口
if(grumpy[l]) s-=customers[l];
l++;
}
return ans+sum;
}
};
题目2:1461. 检查一个字符串是否包含所有长度为 K 的二进制子串 - 力扣(LeetCode)
这道题目很简单,只要统计可以形成的长度为k的二进制字符串的数量即可,长度为k的二进制字符串的总数量应该是记为aim,最后只要比较收集到的二进制字符串数量和aim是否相等即可。代码如下:
class Solution {
public:
bool hasAllCodes(string s, int k) {
unordered_set<string> us;//记录不同的二进制字符串
string str;
int aim = pow(2,k);//长度为k的二进制应该有的字符串数量
for(int l = 0,r = 0;r<s.size();r++){
//入窗口
str += s[r];
if(r < k - 1) continue;
//更新答案
us.insert(str);
//出窗口
str = str.substr(1);//移除第一个字符
l++;
}
return us.size() == aim;
}
};
题目3:2841. 几乎唯一子数组的最大和 - 力扣(LeetCode)
该题也很简单,除了滑动窗口,我们还需要定义一个哈希表记录滑动窗口内有多少个互不相同的元素,如果哈希表的大小大于等于m才能更新答案,题目很简单直接上代码:
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
long long ans = 0;
//窗口长度为k
//套用定长滑动窗口模板
unordered_map<int,int> mp;//记录窗口内不同的元素个数
long long sum = 0;
for(int i = 0,left = 0;i<nums.size();i++){
//入窗口
mp[nums[i]]++;
sum += nums[i];
if(i < k-1) continue;
//更新答案
if(mp.size() >= m) ans = max(ans,sum);
//出窗口
int out = nums[left++];
mp[out]--;
sum -= out;
//如果出现次数为0,就删掉
if(mp[out] == 0){
mp.erase(out);
}
}
return ans;
}
};
题目4:2461. 长度为 K 子数组中的最大和 - 力扣(LeetCode)
该题和上一题不能说相似,只能说是一模一样!上一题要求至少m个不同,本题要求全不相同。全不相同的话也就是说不相同的个数为子数组长度k。直接在上一题的基础上修改更新答案的条件即可。代码如下:
class Solution {
public:
long long maximumSubarraySum(vector<int>& nums, int k) {
long long ans = 0;
unordered_map<int,int> countMap;
long long sum = 0;
//长度为k的滑窗,套用模板
for(int left = 0,right = 0;right < nums.size();right++){
//入窗口
countMap[nums[right]]++;
sum += nums[right];
if(right < k-1) continue;
//更新答案
if(countMap.size() == k) ans = max(ans,sum);
//出窗口
sum -= nums[left];
countMap[nums[left]]--;
if(countMap[nums[left]] == 0) countMap.erase(nums[left]);
left++;
}
return ans;
}
};
题目5:1423. 可获得的最大点数 - 力扣(LeetCode)
第一次做该题是不是有点无从下手,这题该怎么和滑动窗口联系起来呢?
正难则反 ,最后求拿走的k张牌点数之和最大,因为总的点数是固定不变的,那么问题可以转化为剩下的n-k张牌点数之和最小,此外滑动窗口还要求连续这个条件也是可以满足的,因为每次拿牌都是在两边拿,最后剩下的牌一定是连续的。所以最终的答案就是总点数减去n-k张牌最小的点数和。代码如下:
class Solution {
public:
int maxScore(vector<int>& cardPoints, int k) {
/*选走了k个数,还剩下n-k个数,因为总和是固定的选走k个最大化的话,剩下的就是最小的*/
int sum = reduce(cardPoints.begin(),cardPoints.end());
int n = cardPoints.size();
if(k == n) return sum;
int len = n-k;
int ans = INT_MAX;
int s = 0;//维护窗口内的和
for(int l = 0,r = 0;r<n;r++){
//入窗口
s+=cardPoints[r];
if(r < len-1) continue;
//更新答案
ans = min(ans,s);
//出窗口
s-=cardPoints[l++];
}
return sum-ans;
}
};
题目6:1297. 子串的最大出现次数 - 力扣(LeetCode)
该题处理子串中不同字母的数目小于等于maxLetters和第3题、第4题一致,本题只需要想清楚一个关键问题,最后返回出现次数最大的子串出现次数,子串的长度于等于 minSize
且小于等于 maxSize
。假如有一个长度为maxSize的子串出现的次数最多,例如"abcd",那么是不是一定有一个出现次数一样多的子串"abc"?因为"abc"的长度更小,所以可能出现的次数最多,就是说最终出现次数最多的字符串长度一定为minSize。想清楚这个问题代码实现就很简单了。
class Solution {
public:
int maxFreq(string s, int maxLetters, int minSize, int maxSize) {
int ans = 0;
unordered_map<char,int> cnt;
for(int l = 0,r = 0;r<s.size();r++){
//入窗口
cnt[s[r]]++;
if(r < minSize-1) continue;
//更新答案
if(cnt.size() <= maxLetters) ans =
//出窗口
char out = s[l];
if(--cnt[out]==0) cnt.erase(out);
l++;
}
return ans;
}
};
题目7:2653. 滑动子数组的美丽值 - 力扣(LeetCode)
本题很容易想到用滑动窗口解决,每次形成窗口记录第x小的负数即可,但是如何计算出第x小的负数是本题的难点。排序的话时间复杂度太高,那么该如何解决呢?观察本题的数据范围 -50 <= nums[i] <= 50 这很容易想到计数排序,由于数组下标不能为负数,所以我们可以将[-50,50]区间内的数映射到[0,100]。那么找第x小的负数就很容易了,负数区间为[-50,-1]经过映射后区间变为[0,49],所以我们只需要遍历数组下标为0-49的数字。
class Solution {
public:
vector<int> getSubarrayBeauty(vector<int>& nums, int k, int x) {
int n = nums.size();
vector<int> ans(n-k+1,0);
int idx = 0;//ans填到哪个位置了
int cnt[101];
//长度为k的滑窗 + 计数排序
for(int left = 0,right = 0;right<nums.size();right++){
//入窗口
int in = nums[right];
cnt[in+50]++;
if(right < k-1) continue;
//更新答案
int y = x;
//0-49说明是负数
for(int i = 0; i<50;i++){
y -= cnt[i];
//说明找到了
if(y <= 0){
ans[idx] = (i-50);
break;
}
}
//出窗口
int out = nums[left];
cnt[out+50]--;
left++;
idx++;
}
return ans;
}
};
至此,所有定长滑动窗口的题目已经更新完毕!希望你我都能彻底掌握定长滑动窗口!!!!