滑动窗口
- 维持左右边界都不回退的一段范围,求解子数组、子串相关问题
- 求子数组以每个位置开头或者结尾情况下的答案
- 找范围和答案指标之间的单调性关系
#include <vector>
#include <valarray>
using namespace std;
class Solution {
public:
// 时间复杂度 O(n)
int minSubArrayLen(int target, vector<int> &nums) {
int res = INT_MAX;
for (int l = 0, r = 0, sum = 0; r < nums.size(); r++) {
// 查看以 nums[r] 结尾的子数组中是否有符合条件的
sum += nums[r];
// 尽量减小以 nums[r] 结尾的子数组长度
while (sum - nums[l] >= target) {
sum -= nums[l];
l++;
}
// 记录所有位置结尾中的最短数组
if (sum >= target) res = min(res, r - l + 1);
}
return res == INT_MAX ? 0 : res;
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.length();
int res = 0;
// 记录每个字符上次出现的位置
vector<int> last(128, -1);
for (int l = 0, r = 0; r < len; r++) {
// 更新窗口左边界
l = max(l, last[s[r]] + 1);
// 记录最大窗口长度
res = max(res, r - l + 1);
// 更新当前字符最后一次出现的位置
last[s[r]] = r;
}
return res;
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// 记录当前在滑动窗口中的字符
vector<bool> entered(128, false);
int res = 0;
for (int l = 0, r = 0; r < s.length(); r++) {
char ch = s[r];
if (entered[ch]) {
// 已经在窗口中,从窗口左边弹出元素,直到弹出 ch
while (l < r && s[l] != ch) {
entered[s[l]] = false;
l++;
}
l++;
} else {
// 不在窗口中
entered[ch] = true;
res = max(res, r - l + 1);
}
}
return res;
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
// 题目默认如果存在则唯一
string minWindow(string s, string t) {
int lenS = s.length();
int lenT = t.length();
if (lenS < lenT) return "";
// 负数表示 t 中字符在 s 中需要出现的次数,非负数表示其他字符在 s 中出现的次数
vector<int> map(256, 0);
for (char ch: t) map[ch]--;
// 最小覆盖子串的长度
int len = INT_MAX;
// 最小覆盖子串的起始下标
int start = 0;
// 需要出现的各个字符的总个数
int count = lenT;
for (int r = 0, l = 0; r < lenS; ++r) {
// 是 t 中的字符,则出现总次数减一
if (map[s[r]] < 0) count--;
// 当前字符出现次数加一
map[s[r]]++;
// 窗口已经包含 t 中所有字符,即 s 中已经出现了覆盖子串
if (count == 0) {
// 大于 0 说明可以从窗口左边移除一部分重复的字符,从而缩小窗口大小
// 小于等于 0 时不能移除,否则就凑不齐t中的字符
while (map[s[l]] > 0) {
map[s[l]]--;
l++;
}
if (r - l + 1 < len) {
// 记录窗口大小
len = r - l + 1;
// 记录窗口起始位置,用于返回最小覆盖子串
start = l;
}
}
}
if (len == INT_MAX) return "";
return s.substr(start, len);
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
int n = gas.size();
// 汽油余量前缀和
int prefixSum = 0;
// 当前窗口大小
int len = 0;
// 从每个位置开始尝试
for (int l = 0, r; l < n; ++l) {
while (prefixSum >= 0) {
// 可以绕一圈
if (len == gas.size()) return l;
// 计算窗口右边界
r = (l + len) % n;
// 窗口扩大,r 移入窗口
len++;
// 到达 gas[r] 后,计算到达再后面一站的汽油余量
prefixSum += gas[r] - cost[r];
}
// 移除窗口左侧
len--;
// 前缀和也要去除掉 l 位置的影响
prefixSum -= gas[l] - cost[l];
}
return -1;
}
};
#include <vector>
using namespace std;
class Solution {
public:
int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
int n = gas.size();
// 从每个位置开始尝试
for (int l = 0, r = 0, gasLeft; l < n; l = r + 1, r = l) {
// 汽油余量
gasLeft = 0;
// 如果能到达下一站
while (gasLeft + gas[r % n] - cost[r % n] >= 0) {
// 可以绕一圈
if (r - l == n) return l;
// 到达 gas[r] 后的汽油余量
gasLeft += gas[r % n] - cost[r % n];
r++;
}
}
return -1;
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
// 能否通过修改长度为 len 的滑动窗口,把字符串变成平衡字符串
bool ok(vector<int> &counts, int len, int require) {
for (int i = 0; i < 4; ++i) {
// 窗口外的字符频率若超过 require,则无法消去多出来的字符
if (counts[i] > require) return false;
// 用长度 len 的窗口补齐每个字符缺失的个数 require - counts[i]
len -= require - counts[i];
}
// 窗口刚好用完
return len == 0;
}
int balancedString(string s) {
int lenS = s.length();
// 每种字符必须出现的次数
int require = lenS / 4;
// Q W E R转换成 0 1 2 3
vector<int> nums(lenS);
// 统计窗口外字符出现次数
vector<int> counts(4, 0);
for (int i = 0; i < lenS; ++i) {
nums[i] = s[i] == 'Q' ? 0 : (s[i] == 'W' ? 1 : (s[i] == 'E' ? 2 : 3));
counts[nums[i]]++;
}
// 最多调整整个数组
int res = lenS;
// 窗口 [l, r)
for (int l = 0, r = 0; l < lenS; ++l) {
while (!ok(counts, r - l, require) && r < lenS) {
// 窗口右边移入,移入的字符在 counts 中的计数减一
counts[nums[r]]--;
r++;
}
if (ok(counts, r - l, require))
res = min(res, r - l);
// 窗口左边移出,移出的字符在 counts 中的计数加一
counts[nums[l]]++;
}
return res;
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int balancedString(string s) {
int lenS = s.length();
// 每种字符必须出现的次数
int require = lenS / 4;
// Q W E R转换成 0 1 2 3
vector<int> nums(lenS);
// 统计窗口外字符出现次数
vector<int> counts(4, 0);
for (int i = 0; i < lenS; ++i) {
nums[i] = s[i] == 'Q' ? 0 : (s[i] == 'W' ? 1 : (s[i] == 'E' ? 2 : 3));
counts[nums[i]]++;
}
int debt = 0;
for (int i = 0; i < 4; i++) {
if (counts[i] < require) {
// 低于需要出现的次数就改成 0
counts[i] = 0;
} else {
// 高于需要出现的次数就改成负数,表示多出来的个数
counts[i] = require - counts[i];
// 计算一共多出来多少个
debt -= counts[i];
}
}
// 一个都没多,说明刚好平衡
if (debt == 0) return 0;
int res = INT_MAX;
// todo
for (int l = 0, r = 0; r < lenS; r++) {
if (counts[nums[r]]++ < 0) debt--;
if (debt == 0) {
while (counts[nums[l]] > 0)
counts[nums[l++]]--;
res = min(res, r - l + 1);
}
}
return res;
}
};
#include <vector>
using namespace std;
class Solution {
public:
vector<int> counts;
// 返回 nums 的所有子数组中,数字种类不超过 k 的子数组个数
int numsOfMostKinds(vector<int> &nums, int k) {
counts.clear();
counts.resize(20001, 0);
int res = 0;
for (int l = 0, r = 0, types = 0; r < nums.size(); r++) {
// 窗口右侧移入 nums[r]
// 种类数加一
if (counts[nums[r]] == 0) types++;
// 词频加一
counts[nums[r]]++;
// 种类数超过了,需要从窗口左侧移除
while (types > k) {
// 词频减一
counts[nums[l]]--;
// 刚好把这个字符全部移除
if (counts[nums[l]] == 0) types--;
l++;
}
// 累加的是以 nums[r] 结尾,数字种类不超过 k 的子数组个数
res += r - l + 1;
}
return res;
}
int subarraysWithKDistinct(vector<int> &nums, int k) {
return numsOfMostKinds(nums, k) - numsOfMostKinds(nums, k - 1);
}
};
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int longestSubstring(string s, int k) {
int len = s.length();
vector<int> counts;
int res = 0;
// 子串只有 require 种字符,每种字符都必须大于等于 k 次,返回这样的最长子串
for (int require = 1; require <= 26; ++require) {
counts.clear();
counts.resize(256, 0);
// 窗口中字符种类总数
int types = 0;
// 窗口中字符出现次数大于等于 k 的种类总数
int satisfy = 0;
for (int l = 0, r = 0; r < len; r++) {
counts[s[r]]++;
// 新出现了一个种类
if (counts[s[r]] == 1) types++;
// 新达标了一个种类
if (counts[s[r]] == k) satisfy++;
// 字符种类超了,开始移除左边字符
while (types > require) {
if (counts[s[l]] == 1) types--;
if (counts[s[l]] == k) satisfy--;
// 窗口左侧移出字符
counts[s[l]]--;
l++;
}
// 子串以 r 位置结尾,且种类等于 require 的最大长度
if (satisfy == require) res = max(res, r - l + 1);
}
}
return res;
}
};