滑动窗口方法
-
参考了一位大佬的公众号文章,公众号名为“labuladong”,参考文章为《我写了套框架,把滑动窗口算法变成了默写题》
一、LeetCode 76: 最小覆盖字串(hard)
1. c++实现
- ps: 刚开始刷题,很菜,本题代码自己先尝试写了一下,写出来后细节上问题很多,最后看着文章的示例对了一遍,基本算是copy,此处感谢文章作者。
class Solution {
public:
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; //记笔记,INT_MAX
//窗口的右边先开始滑动
while(right<s.size()){
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 - start;
}
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);
}
};
int main(){
Solution s_test;
string result;
result = s_test.minWindow("ADOBECODEBANC","ABC");
cout<<result<<endl;
system("pause");
}
2. python3实现
import collections
class Solution:
def minWindow(self, s: str, t: str) -> str:
need = collections.defaultdict(lambda:0)
window = collections.defaultdict(lambda:0)
s = list(s)
t = list(t)
for i in t:
need[i] = need[i] + 1
left = 0
right = 0
valid = 0
start = 0
length = 100000
while(right < len(s)):
c = s[right]
right = right + 1
if need.__contains__(c):
window[c] = window[c] + 1
if window[c] == need[c]:
valid = valid + 1
while valid == len(need):
if right - left < length:
start = left
length = right - left
d = s[left]
left = left + 1
if need.__contains__(d):
if window[d] == need[d]:
valid = valid - 1
window[d] = window[d] - 1
if length != 100000:
result = ''
for j in s[start:start+length]:
result = result + j
return result
else:
return ""
二、LeetCode 567 :字符串排序(Medium)
1. c++实现
class Solution {
public:
bool checkInclusion(string s1, string s2) {
//以下两句的做法与上一题同,基本思想是创建两个哈希表,一个表用来存“理想”,一个表用来存“现实”,当现实和理想一样时,就找到情境中的问题的解了
unordered_map<char,int> need,window;
for(char c:s1) need[c]++;
int left = 0, right = 0;
int len = s1.size();//这儿的指标是找到一个和目标字符串“等长”的子串,上题的目标是“最小”
int valid = 0;
// 滑动右边窗口的代码不需要动
while(right<s2.size()){
char c = s2[right];
right++;
if(need.count(c)){
window[c]++;
if(window[c]==need[c]){
valid++;
}
}
//我最开始写的时候是以window是否完全覆盖need作为入口条件,进去之后再判断找出来的子串是否与目标串等长,结果给自己挖了个坑,找了半天。因为我照搬了上题的模板,滑动左窗口时最外层时一个while,内部的left++被反复执行,所以比较子串与目标串那句每次读的都是更新后的left值,导致最后返回的是“是否有包含s1的排列的子串”的判断
while(right - left >= len){ //保证选出来的串始终与目标串等长,绕开了上面描述的坑
if(valid == need.size()){cout<<"True"<<endl;return true;}
char d = s2[left];
if(need.count(d)){
if(window[d]==need[d])valid--;
window[d]--;
}
left++;
}
}
cout<<"False"<<endl;
return false;
}
};
2. python实现
import collections
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
need = collections.defaultdict(lambda:0)
window = collections.defaultdict(lambda:0)
for i in s1:
need[i] = need[i] + 1
left = 0
right = 0
valid = 0
length = len(s1)
while(right < len(s2)):
while(left + length > right): #保证子串和目标串等长,乌龟追上来之前,兔子先别跑
c = s2[right]
if need.__contains__(c):
window[c] = window[c] + 1
if need[c] == window[c]:
valid = valid + 1
if valid == len(set(s1)):
return True
right = right + 1
if right > len(s2) - 1: #结构设计好像有点问题,打个补丁。。
break
# 执行下面的语句块之前子串和目标串等长是自然满足的,乌龟往前赶一步,给兔子的里程充点值
d = s2[left]
if need.__contains__(d):
if window[d] == need[d]:
valid = valid - 1
window[d] = window[d] -1
left = left + 1
return False
三、 LeetCode 438 :找所有字母异位词(Medium)
1. c++实现
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
//此处两句固定套路了
unordered_map<char,int> need,window;
for(char c : p) need[c]++;
int left = 0,right = 0;
int valid = 0;
int len = p.size();
vector<int> location;
while(right<s.size()){
while(left+len>right){ //这一句限定窗口在达到目标大小之后右边界没法继续滑动,除非左边窗口释放一个位置
//固定套路+1
char c = s[right];
right++;
if(need.count(c)){
window[c]++;
if(window[c]==need[c])valid++;
}
if (valid == need.size())location.push_back(left);
if(right>s.size()-1)break;
}
//固定套路+1
char d = s[left];
if(need.count(d)){
if(window[d]==need[d])valid--;
window[d]--;//注意这句不要放到上一句前面
}
left++;
}
return location;
}
};
2. python实现
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
need = collections.defaultdict(lambda:0)
window = collections.defaultdict(lambda:0)
s = list(s)
p = list(p)
l = len(p)
for i in p:
need[i] = need[i] + 1
left = 0
right = 0
valid = 0
result = []
while(right < len(s)):
c = s[right]
right = right + 1
if need.__contains__(c):
window[c] = window[c] + 1
if window[c] == need[c]:
valid = valid + 1
if valid == len(need):
result.append(left)
if right-left >= l:
d = s[left]
left = left + 1
if need.__contains__(d):
if window[d] == need[d]:
valid = valid - 1
window[d] = window[d] - 1
return result
ps:此类问题的模板是两层循环,外层右边框,内层左边框。但是第二道题和第三道题因为限定了目标子串的长度,所以只需要一层循环就可以了(实际上在第一次达到子串长度后每个循环体每次都只执行一次)
四、LeetCode 3:最长无重复子串(Medium)
1. c++实现
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> window;
int left=0,right=0;
int len = 0;
while(right<s.size()){
char c = s[right];
right++;
window[c]++;//我最开始写的时候在这句上面加了个判断条件,当时的思路是某个字符的个数超过1了,我就不让你加了,这样做的结果是right++这一句放哪里都不合适。。。回想一下自己简直就是脑子瓦特了,窗口要滑动的,滑动了就要计数,你不让计数窗口怎么滑动,不让窗口滑动那没法玩了。本题中right++和之后的存数据是一套组合拳,其他花样在其他地方整,不能插在这二者之间。
while(window[c]>1){
char d = s[left];
window[d]--;
left++;
}
len = max(len, right - left);
}
return len;
}
};
int main(){
Solution s;
int result;
result = s.lengthOfLongestSubstring("bbbacbb");
cout<<result<<endl;
system("pause");
}
**ps:这道题是四道里面“最简单的”,但我卡在这道题的时间最久。。。可能饭点到了脑子里缺血糖了吧。 **
2. python实现
import collections
class Solution:
def lengthOfLongestSubstring(self, s: str) -> str:
window = collections.defaultdict(lambda:0)
s = list(s)
left = 0
right = 0
start = 0
length = 0
while(right < len(s)):
c = s[right]
right = right + 1
window[c] = window[c] + 1
while( window[c]> 1 ):
d = s[left]
left = left + 1
window[d] = window[d] - 1
if right - left > length:
length = right - left
return length
五、乘最多水的容器
5.1 c++实现
class Solution {
public:
int maxArea(vector<int>& height) {
int left= 0, right = height.size()-1;//最开始再这儿初始化时为了迎合题干,把索引加了1(其实没必要),后面操作数组元素时又忘了这个设定改动,导致发生了数组溢出的异常
int max_capacity = 0;
while( left < right){
//如果有必要,更新最大容量
int capacity = (right - left)*min(height[left],height[right]);
if (capacity > max_capacity){
max_capacity = capacity;
}
//窗口滑动条件,限制容器容量的是最短那条边
if(height[left] < height[right]){
left++;
}
else if(height[left] > height[right]){
right--;
}
//如果左右两条边一样长,随便选一个方向滑动,容量结果是一样的;如果要求给出位置的话,结果会不同
else {
left++;
}
}
cout<<max_capacity<<endl;
return max_capacity;
}
};
5.2 python实现
class Solution:
def maxArea(self, height: List[int]) -> int:
left = 0
right = len(height) - 1
area = 0
if height[left] > height[right]:
max_capacity = height[right]*(right-left)
else:
max_capacity = height[left]*(right-left)
while(left < right):
if height[left] < height[right]:
left = left + 1
else:
right = right - 1
if left == right:
break
if height[left] > height[right]:
area = height[right]*(right - left)
else:
area = height[left]*(right - left)
if area > max_capacity:
max_capacity = area
return max_capacity
六、总结
1.滑动窗口模型
1)指导思想
- 维护一个窗口、滑动窗口、更新答案。遇到具体问题后,抽象问题模型,看看问题能不能应用到这个解题框架里面
- 需要注意的点:
- 窗口初始化在什么位置(大部分在头部初始化一个左闭右开的空窗口)
- 何时向窗口中添加新元素
- 何时缩小窗口,怎么缩小窗口
- 何时更新结果
2)代码模板(直接copy大佬总结的结果)
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)
", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
ps:坑很多,构建模型要清晰,要熟练!
2. c++语言知识积累
1)unordered_map
C++ 11标准中加入了unordered系列的容器。unordered_map记录元素的hash值,根据hash值判断元素是否相同。从查找、插入上来说,unordered_map的效率都优于hash_map,更优于map;而空间复杂度方面,hash_map最低,unordered_map次之,map最大。unordered_map内部元素是无序的.unordered_map的迭代器是一个指针,指向这个元素,通过迭代器来取得它的值.它的键值分别是迭代器的first和second属性.
成员函数:
=迭代器=========
begin 返回指向容器起始位置的迭代器(iterator)
end 返回指向容器末尾位置的迭代器
cbegin 返回指向容器起始位置的常迭代器(const_iterator)
cend 返回指向容器末尾位置的常迭代器
=Capacity
size 返回有效元素个数
max_size 返回 unordered_map 支持的最大元素个数
empty 判断是否为空
=元素访问=
operator[] 访问元素
at 访问元素
=元素修改=
insert 插入元素
erase 删除元素
swap 交换内容
clear 清空内容
emplace 构造及插入一个元素
emplace_hint 按提示构造及插入一个元素
操作=========
find 通过给定主键查找元素,没找到:返回unordered_map::end
count 返回匹配给定主键的元素的个数
equal_range 返回值匹配给定搜索值的元素组成的范围
Buckets======
bucket_count 返回槽(Bucket)数
max_bucket_count 返回最大槽数
bucket_size 返回槽大小
bucket 返回元素所在槽的序号
load_factor 返回载入因子,即一个元素槽(Bucket)的最大元素数
max_load_factor 返回或设置最大载入因子
rehash 设置槽数
reserve 请求改变容器容量
2) INT_MAX,INT_MIN
int占4字节32位,根据二进制编码的规则,INT_MAX = 2^31-1,INT_MIN= -2^31.C/C++中,所有超过该限值的数,都会出现溢出,出现warning,但是并不会出现error。如果想表示的整数超过了该限值,可以使用长整型long long 占8字节64位。