滑动窗口(双指针)搜索

滑动窗口

【优快云】滑动窗口详解
滑动窗口是一种双指针的方法。两个指针包含的中间的元素是一个窗口。

对于双指针,left和right,窗口的覆盖范围是: [ l e f t , r i g h t ) [left,right) [left,right)是一个左开右闭的区间。也就是说当left==right时,窗口大小是0。

重要的几个方面:

  • 初始窗口的形成
  • 窗口的更新和维护(两个指针的移动)
    • 一般情况下,left和right都执行加操作
    • 那么对left执行加,意味着有数据退出窗口
    • 对right执行加,意味着有数据加入窗口

例题练习与分析

leetcode #76:最小覆盖子串

该题描述

这是一个求解最小窗口的题目,应用滑动窗口的思路是:

  1. 初始化的窗口大小为0,执行第2步。
  2. 更新right指针,递增,直到刚刚能够满足题目要求,然后定死right,执行第3步。
  3. 更新left指针,递增,直到刚刚要使得窗口不能满足题目要求,意味着这是该right下最小的窗口了。此时判断并更新一下记录的最小满足的窗口大小以及窗口信息;再回到第2步。

如此,时间复杂度是: O ( 2 n + m ) O(2n+m) O(2n+m)其中n是s的长度,m是t的长度。

string minWindow(string s, string t) {
        //就是要找一个最小的窗口
        unordered_map<char, int> window;//记录当前窗口中已经覆盖的t中的字符
        unordered_map<char, int> need;  //记录还需要覆盖的t中的字符
        for(int i=0;i<t.size();i++){
            if(need.count(t[i])>0){
                need[t[i]]+=1;
            }
            else{
                need[t[i]]=1;
            }
        }
        int left=0, right=0;
        int start=0, min_size=s.size();
        char cur;
        int sat=0, cur_size=-1;
        while(right<s.size()){
            cur = s[right++];
            if(need.count(cur)>0){
                need[cur]-=1;
                window[cur] = window.count(cur)>0 ? window[cur]+1 : 1;
                if(need[cur]>=0)
                    sat+=1;//又满足了一个了
            }
            if(sat == t.size()){
                //目前情况下t的全部都已经满足了
                //然后需要尝试缩小窗口,缩小之后,left和right规定的窗口时刚好不满足的
                while(left<=right){
                    cur = s[left++];
                    if(window.count(cur)>0 && window[cur]>0){
                        window[cur]-=1;
                        need[cur]+=1;
                        if(need[cur]==1){
                            sat-=1;
                            cur_size=right-left + 1;
                            // cout<<cur_size<<endl;
                            if(cur_size<min_size){
                                start=left-1;
                                min_size=cur_size;
                            }
                            break;
                        }
                    }
                }
            }
        }
        if(cur_size==-1){
            return "";
        }
        return s.substr(start, min_size);
    }

leetcode #438:找到字符串中所有字母异位词

此题描述

异位词的概念:就是原字符串中字符随机打乱
很明显这是一个固定窗口大小的问题,窗口大小是p的长度。
所以在这里重要的是left的指针的更新,也可以倒着做,就是看right怎么更新了。
这里的时间复杂度呢? O ( m + m n ) O(m+mn) O(m+mn)?大概是这样吧。

vector<int> findAnagrams(string s, string p) {
        vector<int> result;
        if(p.size()>s.size()) return result;
        unordered_map<char, int> need;
        unordered_map<char, int> window;
        for(int i=0;i<p.size();++i){
            need[p[i]] = need.count(p[i])>0?need[p[i]]+1:1;
        }
        int left=0, right;
        char cur;
        bool not_in=false, got_more=false;
        int next_start=left, j, i;
        while((right=left+p.size())&& right<=s.size()){
            for(i=next_start;i<right;i++){
                cur = s[i];
                if(need.count(cur)==0){
                    not_in = true;
                    for(j=left;j<i;j++){
                        window[s[j]]=0;
                    }
                    left=i+1;
                    next_start=left;
                    break;
                }
                else{
                    if(window.count(cur)>0 && window[cur]==need[cur]){
                        got_more = true;   
                        window[cur]+=1;
                        for(j=left;j<=i;++j){
                            window[s[j]]-=1;
                            if(window[cur]==need[cur]){
                                left=j+1;
                                next_start=right;
                                break;
                            }
                        }
                    }
                    else{
                        window[cur] = window.count(cur)>0?window[cur]+1:1;
                    }
                }
            }
            if(!(not_in || got_more)){
                result.push_back(left);
                window[s[left]]-=1;
                left++;
                next_start = right;
            }            
            not_in = false;
            got_more = false;
        }

        return result;
    }

leetcode #1658: 将 x 减到 0 的最小操作数

此题描述

据说这个题也可以使用双指针来完成。
那么分析一下,这个题的结果实际上是一个最大的窗口,这个窗口位于数组的中间。

问题一:如何创建初始的窗口

这个题目,左右两边都需要减去一部分,那么初始的窗口可以是这样的:
先按照全部减去左边来做的结果,left的值设置为x可减的最大情况(可能会有剩余),然后right设置为数组长度
或者:可以从若干状态中先找一个减法较少的情况作为初始状态。
滑动窗口(双指针的方法)就像是人工智能里面的一种搜索一样,但它不像BFS/DFS是盲目的,倒有点像启发式搜索方法。

问题二:如何更新双指针

实际上,就是每次固定left,然后搜索right的值看有没有满足题意的,然后更新right无用的情况下,再去更新left。

最终求解的窗口范围: [ l e f t , r i g h t ) [left, right) [left,right)表示不减去这个窗口中的数字。

这个时间复杂度是: O ( n ) O(n) O(n)

int minOperations(vector<int>& nums, int x) {
        //可以使用人工智能里面的搜索算法,这里返回最小操作数,可以使用广度优先搜索,就是一个二叉树
        // return minO(nums, 0, nums.size()-1,  x);//超出时间限制
        // return minO_1(nums, x);//超出时间限制
        int left=0, right=nums.size();
        while(left<right && x>0){
            x-=nums[left++];
            if(x<0){
                x+=nums[--left];
                break;
            }
        }
        if(left==right){
            if(x>0){
                //全加到一起都不行
                return -1;
            }
            else{
                return left;
            }
        }
        int times=-1, temp;
        if(x==0) times = left;
        while(right >= left && left>=0){
            while(x>0){
                //更新right
                temp=x-nums[right-1];
                if(temp>0){
                    x=temp;
                    right-=1;
                    continue;
                }
                else if(temp==0){
                    x=temp;
                    right-=1;
                    temp = nums.size()-(right-left);
                    if(times==-1 || temp < times){
                        times=temp;
                    }
                    break;
                }
                else{
                    break;
                }
            }
            //更新left,此时x>=0
            --left;
            if(left<0)break;
            x+=nums[left];
        }
        
        return times;        
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值