滑动窗口的进阶应用

 一.找到字符串里面所有的异位词

1.题目

 初次看到此题我们一般默认对p 这个字符串里面的字符进行排列组合

“abc”   “acb”  ''bac''     '' bca''   '' cba''  '' cab'' 之后对 s 这个字符串进行暴力遍历,若存在则返回 

“起始”位置的下标

其实,不妨换个思路:p这个字符串里面每一个字符出现的次数是固定的,我们只需要记录每一个

字符出现的次数,对s字符串进行遍历只要是对应字符出现的次数相同,那么就是所谓的“字母异位

词”。

借助2个哈希表:记录每一个字符出现的次数

解法一:暴力求解 + 哈希表

解法二:滑动窗口 + 哈希表

2.算法原理

1)暴力求解:定义2个“哈希表”,hash1 [26]  记录p 里面每一个字符出现的次数

定义2个指针 left = 0,right = 0;

hash2[26] 记录s 这个字符串里面每一个字符出现的次数

同时记录一下p字符串的大小:len,在s 里面每隔len 个字符进行判断一个是否相等

2)滑动窗口:我们发现每一次进行新的比较的时候,right  指针都需要再次回到left 指向的下一个

位置,其实这是没有必要 的,在[let, right]这个区间最大的字符有效个数是固定的,所有每次right

指针可以停留在“原来的位置”,对left 指针进行更新:left 指针++ 

这不“滑动窗口”的思想就来了。

之所以把哈希表的大小设置为26,是因为题目已给出说明:2个字符串的均是由小写字母组成的。

3.分析

4·OJ代码
版本1的代码:
    vector<int> findAnagrams(string& s, string& p)
    {
        vector<int>ret;
        int hash1[26] = {0};//记录p 里面字符出现的次数
        int hash2[26] = {0};//记录s 里面字符出现的次数

        for(auto it :p)
          hash1[it-'a']++;

        int left = 0,right = 0,len = p.size();
        int n = s.size();

        for(;right < n;right++)
        {
            //进窗口
            char in = s[right];
            hash2[in-'a']++;
            //判断 & 出窗口
            if(right-left+1 > len )
            {
                //删除left对应元素出现的次数
                char out = s[left++];
                hash2[out-'a']--;
            }
            //结果更新
            if(right-left+1 == len)
            {
                 int i = 0,j = 0;
                 int flag = 1;//标志位:默认2个哈希表一样
                //先判断2个哈希表是否一致
                while(i < 26)
                {
                    if(hash1[i++] != hash2[j++])
                    {
                        flag = 0;
                        break;
                    }
                }
                if(flag)
                ret.push_back(left);
                else
                flag = 1;//及时更新,便于下一次判断

            }

        }
         // 可能存在righ 走到n的时候,此时元素个数 [left right-1] == len 
         if(len == right-left+1 )
         {
            int i = 0,j = 0;
           int flag = 1;//标志位:默认2个哈希表一样
          while(i < 26)
            {
                if(hash1[i++] != hash2[j++])
                {
                    flag = 0;
                    break;
                }
            }
             if(flag)
               ret.push_back(left);
         }
    return ret;
    }

 咱来看看运行结果咋样~~~

其实对于这个更新结果里面的判断部分咱们可以进行优化的,虽然说优化前后时间复杂度都是

O(N) 级别,但是思想是很巧妙的

 采用计数方法对count :表示滑动窗口内有效的字符个数,注意不同的字符才算有效的字符。

分析见下:

优化后的代码:
  vector<int> findAnagrams(string& s, string& p) 
    {
   
             int hash1[26] = { 0 };//统计p 里面每个字符出现的有效字符个数
    int hash2[26] = { 0 };
    for (auto it : p)
        hash1[it - 'a']++;

    int len = p.size();
    vector<int>ret;
    //滑动窗口
    for (int left = 0, right = 0, count = 0; right < s.size(); right++)
    {
        //进窗口
        // hash2[s[right]-'a']++;
        //进窗口的同时要维护count(滑动窗口的有效字符个数)
        // if (++hash2[s[right] - 'a'] <= hash1[s[right] - 'a'])//同一个字符可能出现不止1次
         char in = s[right];
        if (++hash2[in- 'a'] <= hash1[in - 'a'])//同一个字符可能出现不止1次
            count++;
        //出窗口 这时候只需要出一个元素即可
        //注意 不管hash2[s[left]-'a'] > hash1[p[left]-'a'] 还是<= 都需要--hash2[s[right]-'a']
        if (right - left + 1 > len)
        {
            char out = s[left++];
            if (hash2[out- 'a']-- <= hash1[out - 'a'])//条件不管是否满足left 对应 的元素都需要删除
                count--;//此时删除的是一个有效字符
        }
        //    else

        // hash1[s[left]-'a']--;//删除left 指向的元素
        //结果更新
        if (count == len)
        {
            ret.push_back(left);
        }
        }
        return ret;

    }

 此时的运行效果:

二·串联所有单词的子串

1.题目

其实本题可以简化为找单词的异位词,只不过这里的单词的长度是固定的:words[0] .size 

 这里借助上面找字母异位词的思想,我们发现其实这道题并不是那么困难哈~~~

注意这里有一个关键的条件:就是words  数组里面的所有单词的大小都是固定的

记录为 len = words[0].size(); 注意因为里面每一个元素都是字符串,需要借助size()函数进行求出

每一个单词的大小。

把words这个数组的大小记录为  sz = words.size():表示words 数组里面字符串的个数多少

2.算法原理

滑动窗口 + count 计数

在这直接上“滑动窗口”,对于暴力求解的算法就不在一一叙述了

还是老问题,每当对[left  rigth ]指定的区间进行遍历完后,right 指针没有必要再次回到left 指向的

下一个位置,对于这个区间有效单词的 个数是一定的,只需要一定left 指针所在的位置即可。

依然还是下面的几个步骤:

进窗口+维护count

出窗口+维护count

结果更新+维护count

3.分析

4·OJ代码
    vector<int> findSubstring(string& s, vector<string>& words) 
    {
        unordered_map<string,int> hash1;//记录words里面每一个单词出现的次数
        for(auto it:words)
          hash1[it]++;
        int sz = words.size();//单词的总个数
        int len = words[0].size();//每一个单词的长度
        int n = s.size();
        vector<int>ret;

        for(int i = 0;i< len ;++i)
        {
            int count = 0;//记录滑动窗口内在words里面的单词个数
            unordered_map<string,int>hash2;//记录s里面每一个单词出现的次数

            for(int left = i,right = i; right+len <= n;right += len)//注意条件判断:必须保证进入之后走len步长是一个有效单词
            {
                //进窗口
                string in  = s.substr(right,len);//借助函数取到指定的单词
                if(++hash2[in] <= hash1[in])
                   count++;//进入的是一个有效的单词
                //出窗口
                if(right-left+1 > len*sz)
                {
                    string out = s.substr(left,len);
                    left +=len;//注意这里是移动len个步长
                    if(hash2[out]-- <= hash1[out])
                      count--;//删除的是一个有效单词
                }
                if(count == sz)
                  ret.push_back(left);

            }
        }
        return ret;


    }

 运行结果见下:

三·最小覆盖子串

1.题目

 

2.算法原理

关于滑动窗口的OJ 题目,也是做了不少的,无非就是双指针的移动 + “4步走”

进窗口;判断;出窗口;结果更新。

这个题与之前略微还是有点不同;采用额外的两个变量 kind ,count进行记录

kind 记录:t  字符串有效的字符个数(非首次出现的字符不会进行记录)

count:记录窗口内有效的字符个数

3.分析

4·OJ代码
string minWindow(string& s, string& t) 
    {
        //哈希表+滑动窗口
        int hash1[128] = {0};
        int hash2[128] = {0};
        //开128空间而不开52个空间:涉及到大写字母的问题,在转化为下标的时候,存在溢出或者负数的情况
        int kind = 0;//记录t里面有效(非首次出现的字符就不在进记录)字符的个数
        for(auto it: t)
            if(hash1[it]++ == 0)//只要是首次出现,kind就++
                kind++;
        int count = 0;//记录窗口内有效的字符个数
        int start = -1;//记录子串的起始位置
        int sz = INT_MAX;
        for(int left = 0,right = 0;right < s.size();++right)
        {
            //进窗口
            char in = s[right];
            if(++hash2[in] == hash1[in])
                count++;//有效字符
            //出窗口
            while(count == kind )
            {
                if(right-left+1 < sz)
                 {
                    sz = right-left+1;//最小区间的更新
                    start = left;//位置的更新
                 }

                    //元素删除
                char out = s[left++];
                if(hash2[out]-- == hash1[out])
                    count--;//删除有效字符
            }

        }
        return start == -1 ? string():s.substr(start,sz);    

    }

手搓了这么久的代码,来看看运行结果咋样吧~~~

总结:

以上是关于“滑动窗口”OJ 题目的进阶应用。总的来说,对这一思想的应用是在“暴力求解”的基础上

进行优化,对于滑动窗口条件的结束以及结果的更新都需要进行画图来得到明确的一个答案。所以

说还是思想与画图的综合应用。对于这一部分的应用还得看咱自己的“功夫”是否到位。 

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值