窗口长度变化的代码模板:
//滑动窗口伪代码
class Solution {
public:
int SlideWindow(string s) {
// 同方向移动,起始的时候,都位于 0,表示我们定义搜索区间为 [left, right) ,此时区间为空区间
int left = 0;
int right = 0;
while(right < Slen){
//每一次循环的开始,都一定不满足条件
//(因为上一次循环是从满足条件跳出while的)
// 这里对状态做修改,好让程序在后面检测到满足条件
while(满足条件){
// 对状态做修改,好让程序在后面检测到不满足条件
left++; //右移left
}
//记录当前最接近结果的值
right++; //右移right
}
return maxlen;
}
};
固定窗口长度的代码模板:
//假设滑动窗口固定长度为: n ,则代码模板如下:
class Solution {
public:
int SlideWindow(vector<int>& nums, int k) {
int length = nums.size();
//在进入循环之前,必须先初始化好窗口为最左侧位置的情况
//并且维护好这种情况下的相关变量
//这里要首先判断一下初始化的结果是否满足题意,然后下面的第一次循环就不必
//遍历第一种情况了,这么做是也是为了满足循环不变量[i - n, i)
//循环不变量:滑动窗口[i - n, i),窗口长度固定为 n
for (int i = n; i < length; i++) {
//此时i位置为窗口本次循环的末位置下标,由于是开区间i,所以接下来要维护nums[i]的状态
//而i-n位置为上一次循环的首位下标,我们通常也需要关注维护它的状态,使窗口左边界向右移动一位
//以上操作进行完毕之后,此时窗口区间就变为闭区间[i - n + 1, i]了,长度还是n
//下一次循环之前i++,区间再次变为半开半闭状态-[i - n, i)
}
return ...;
}
};
例题1:leecode第3题:无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长
子串
的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
解答:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.size();
if(len < 2){
return len;
}
int *freq = new int[128];
for(int i = 0; i < 128; i++){
freq[i] = 0;
}
int maxlen = 1;//维护一个变量用于记录最长字符串的长度
int left = 0, right = 0;
//循环不变量 [left, right)无重复字符串
while(right < len){
freq[s[right]]++; // 对状态做修改,好让程序在后面检测到满足条件:[left, right)出现重复元素
right++;
while(freq[s[right-1]] == 2){//满足条件:[left, right)出现重复元素
freq[s[left]]--;
left++;
}
maxlen = maxlen < (right - left) ? (right - left) : maxlen;//记录当前最接近结果的值
}
return maxlen;
}
};
例题2:lecoode第76题:最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
解答:
class Solution {
public:
string minWindow(string s, string t) {
int s_len = s.size();
int t_len = t.size();
int count = 0; //记录t包含的字母个数
vector<int> MinWindow(128, 0); //记录滑动窗口各个字母出现的次数
vector<int> CharArray(128, 0); //记录t包含各个字母出现的次数
//记录t每个字母出现的次数
for(int i = 0; i < t_len; ++i)
{
++CharArray[t[i]];
}
//记录t有多少个字母
for(int i = 0; i < 128; ++i)
{
if(CharArray[i] > 0)
{
++count;
}
}
int left = 0; //滑动窗口的左边
int right = 0; //滑动窗口的右边
int m_left = 0; //记录最小子串在s的起始位置
int minLen = s_len + 1; //记录最小子串的长度
int sameNumber = 0; //记录s中与t相同的字母的个数
while(right < s_len)
{
char rc = s[right];
//这个字母在t中出现
if(CharArray[rc] > 0)
{
//将这个字母加入到记录滑动窗口的数组中
++MinWindow[rc];
//此时这个字母在s出现的次数等于在t出现的次数,即s中这个字母满足覆盖t子串的要求
if(MinWindow[rc] == CharArray[rc])
{
++sameNumber;
}
}
++right;
//当s中满足t中所有出现的字母要求
while(sameNumber == count)
{
//维护当前最小字符串的起始和偏移
if(minLen > right - left)
{
minLen = right - left;
m_left = left;
}
char lc = s[left];
//对滑动窗口将要做边界右移会造成的状态进行统计
if(CharArray[lc] > 0)
{
--MinWindow[lc];
if(MinWindow[lc] < CharArray[lc])
{
--sameNumber;
}
}
//滑动窗口左边界右移
++left;
}
}
//如果没进入sameNumber == count循环,证明s是不满足包含t的条件,则返回空字符串
return minLen == 1 + s_len ? "" : s.substr(m_left, minLen);
}
};
例题3:leecode lcr第8题:长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
解答:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = nums.size();
//先判断特殊情况,如果数组总和都小于targer,则返回0(即示例3的情况)
int sum = 0;
for(int i = 0; i < len; i++){
sum += nums[i];
}
if(sum < target) return 0;
int WindowSum = 0; //记录当前窗口的元素和
int min_len = len; //维护一个最小长度
int left = 0, right = 0;
while(right < len){
WindowSum += nums[right];
right++;
while(WindowSum >= target){
min_len = (min_len < right - left) ? min_len : (right - left);
WindowSum -= nums[left];
left++;
}
}
return min_len;
}
};
例题4:leecode 438. 找到字符串中所有字母异位词:(固定窗口大小的滑动窗口问题)
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
示例 2:
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
提示:
1 <= s.length, p.length <= 3 * 104
s 和 p 仅包含小写字母
解答:
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int pLen = p.size(), sLen = s.size();
vector<int> ans;
if(pLen > sLen){
return ans;
}
vector<int> pFreq(26);
vector<int> wFreq(26);
for(int i = 0; i < pLen; i++){
pFreq[p[i] - 'a']++;
wFreq[s[i] - 'a']++;
}
if(IsArrEq(pFreq, wFreq)){
ans.push_back(0);
}
//滑动窗口长度是pLen
for(int i = pLen; i < sLen; i++){
wFreq[s[i - pLen] - 'a']--;
wFreq[s[i] - 'a']++;
if(IsArrEq(pFreq, wFreq)){
ans.push_back(i - pLen + 1);
}
}
return ans;
}
int IsArrEq(vector<int> arr1, vector<int>arr2){
for(int i = 0; i < 26; i++){
if(arr1[i] != arr2[i])
return 0;
}
return 1;
}
};
例题5:leecode 1423. 可获得的最大点数:(固定窗口大小的滑动窗口问题)
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。
每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。
你的点数就是你拿到手中的所有卡牌的点数之和。
给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。
示例 1:
输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。
示例 2:
输入:cardPoints = [2,2,2], k = 2
输出:4
解释:无论你拿起哪两张卡牌,可获得的点数总是 4 。
示例 3:
输入:cardPoints = [9,7,7,9,7,7,9], k = 7
输出:55
解释:你必须拿起所有卡牌,可以获得的点数为所有卡牌的点数之和。
示例 4:
输入:cardPoints = [1,1000,1], k = 1
输出:1
解释:你无法拿到中间那张卡牌,所以可以获得的最大点数为 1 。
示例 5:
输入:cardPoints = [1,79,80,1,1,1,200,1], k = 3
输出:202
提示:
1 <= cardPoints.length <= 10^5
1 <= cardPoints[i] <= 10^4
1 <= k <= cardPoints.length
解答:
class Solution {
public:
int maxScore(vector<int>& cardPoints, int k) {
//固定窗口大小为len - k,查找此窗口的最小值,返回“数组和 - 最小值“即可
int len = cardPoints.size(), n = len - k, winSum = 0;
for(int i = 0; i < n; i++){
winSum += cardPoints[i];
}
int minSum = winSum;
for(int i = n; i < len; i++){
winSum -= cardPoints[i - n];
winSum += cardPoints[i];
minSum = min(minSum, winSum);
}
int sum = 0;
for(int i = 0; i<len; i++){
sum += cardPoints[i];
}
return sum - minSum;
}
};