zoukankan      html  css  js  c++  java
  • 滑动窗口

    滑动窗口方法


    一、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位。


  • 相关阅读:
    学习笔记 MYSQL报错注入(count()、rand()、group by)
    学习笔记 HTTP参数污染注入
    学习笔记 MSSQL显错手工注入
    代码审计入门后审计技巧
    字符串的排列
    二叉搜索树与双向链表
    复杂链表的复制
    二叉树中和为某一值的路径
    二叉搜索树的后序遍历序列
    从上往下打印二叉树
  • 原文地址:https://www.cnblogs.com/huhu555/p/14660688.html
Copyright © 2011-2022 走看看