双指针
-
字符串的题目优先考虑滑动窗口
找字符串中的最大回文字符串,中心向两边扩散的双指针 -
数组有序,确定满足条件的多个元素
有序数组中确认2个数和为目标值,left right双指针
有序数组中确认3个数和为目标值,for遍历,left right双指针最接近的三数和
https://leetcode.cn/problems/3sum-closest/
先sort排序,遍历数组,在后续数组使用双指针
滑动窗口
滑动窗口需要双指针left right作为窗的左右边界
- 窗口长度固定
right - left >= s.size() 只要窗长度比目标长度大,就要缩窗,一位位移动 - 窗口长度变化
求满足题目要求的最小窗口或者最大窗口,一般是valid == need.size()
小抄上的题目都是while(right < s.size())开始的滑动窗口
也有for循环中使用while(sum > target)
需要灵活变通
滑动窗口是双指针更为复杂的应用
关于子串的问题,实际上都可以使用滑动窗口解题,主要分为两种情况
- 子串str1在子串str2中连续分布
- 子串str1在子串str2中离散分布,中间穿插其它字符,要找满足要求的最短子串
定义两个map,need和windows,need统计str1中字符元素个数,windows统计当前滑窗中且元素在need中
当前元素是need中元素,窗口右指针后移,窗中某元素个数和need中元素个数相等,valid++
当前元素不是need中元素,窗口左指针后移,valid清0
valid个数等于need.size(),返回true,如果窗口滑动到末尾还未确定返回false
直接套用模板
string minWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, len = INT_MAX;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 扩大窗口
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (valid == need.size()) {
// 在这里更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
// d 是将移出窗口的字符
char d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
// 返回最小覆盖子串
return len == INT_MAX ?
"" : s.substr(start, len);
}
// 此题滑动窗口的窗没有理解正确
// 窗可能添加元素到末尾,包含了很多重复的元素,这个时候需要缩窗,看是否能满足要求
// 没有缩窗的过程,"adc", "dcda"最后统计d元素有2个,而目标元素只有一个,会错误返回false(只考虑到元素没有的情况,元素一直有的情况没有考虑到)
// 滑窗模板不熟练,窗口元素的统计比较方法不需要重新写一个函数
// 每次遍历一个元素的都会统计比较,同时每次都会内层while循环判断,一旦条件满足,考虑缩窗
// 目标 map<int, char> need
// map<int, char> window;
class Solution {
public:
bool checkInclusion(string s1, string s2) {
map<int, char> need;
map<int, char> window;
int valid = 0;
for (int s1Char : s1) {
need[s1Char]++;
}
int right = 0;
int left = 0;
while (right < s2.size()) {
char curCh = s2[right];
right++;
if (need.count(curCh)) {
window[curCh]++;
if (window[curCh] == need[curCh]) {
valid++;
}
}
while (right - left >= s1.size()) { // right在上面已经加1,right - left就是当前窗的长度,用need的长度不准确,需要用s1的长度
if (valid == need.size()) {
return true; // 获取最大值/直接返回
}
char cur = s2[left]; // 移出元素
left++; // 窗口右移
if (need.count(cur)) {
if (window[cur] == need[cur]) {
valid--;
}
window[cur]--; // 窗元素在统计之后,窗元素统计:增加窗元素在统计之前,删除窗元素,在统计之后
}
}
}
return false;
}
};
最短数组(窗口和问题,sum类似窗口)
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的子数组。如果不存在符合条件的子数组,返回 0。
输入: [2,3,1,2,4,3], s = 7
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。
自己的版本
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
// map<int, int> need;
// map<int, int> window;
// int valid = 0;
int sum = 0;
int minLen = INT_MAX;
int left = 0;
int right = 0;
while (right < nums.size()) {
sum += nums[right]; // sum实际就是窗口
right++;
while (sum >= target) {
//if (sum == target) {
minLen = min(right - left, minLen);
//}
sum -= nums[left]; // 移出窗口元素
left++;
}
}
return minLen;
}
};
窗口和简化计算过程版本
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int res = INT_MAX;//用于记录滑动窗口长度
int left = 0; //左窗口
int right = 0; //右窗口
int sum = 0; //窗口和大小
for (right = 0; right < nums.size(); right++) {
sum = sum + nums[right];
while (sum >= target) {
//找到符合的窗口,记录数据,并且继续缩小窗口看是否符合,不符合继续退出去循环
res = min(right - left + 1, res);
//缩小窗口
sum = sum - nums[left++];
}
}
return res = res == INT_MAX ? 0 : res;
}
};