滑动窗口

滑动窗口方法


一、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)\n", 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位。


posted @ 2021-04-15 00:35  呼_呼  阅读(165)  评论(0编辑  收藏  举报