Excaliburer`s Zone

It was challenging, but not risky.

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一.题目链接:https://leetcode.com/problems/minimum-window-substring/

二.题目大意:

 给定两个字符串S和T,要求从S中找出包含T中所有字母的最短子串,同时要求时间复杂度为O(n)。

三.题解:

这道看上去和https://leetcode.com/problems/longest-substring-without-repeating-characters最长不重复子序列类似,似乎都可以利用滑窗法来解决(当然,此处的窗口大小必然是动态的,不固定的)。然而不同的是,S中的字符可能包含T中不存在的字符,也就是说:在滑窗构建的过程中(遍历S的过程中),会遇到两种情况,当前字符不是T中的字符和当前字符是T中的字符。那么如何构建滑窗呢?具体如下:

(1)我们设置两个指针left和right,分别表示滑窗的两端。哈希表_map用于存储T中出现的字符以及相应的个数。cnt用于统计目前滑窗中出现的T中的字符的个数。

(2)right指针不停的向右移动,并且判断此处的字符是否是T中的字符,是的话,计数器cnt加1,同时_map中相应的字符对应的次数减去1。当cnt的数值与T的长度相同时,表示我们已经构造完成了一个符合条件的滑窗了。

(3)由于,整个S中可能存在多个符合条件的滑窗,所以当我们找到一个符合条件的滑窗时,需要记录相应的长度和起始下标;然后再去构建下一个滑窗。

(4)如何构建下一个滑窗呢?此时,就需要操作左指针left了,将left向右移动,若left处的字符是T中的字符的话,则cnt减去1。相应的_map处的字符对应的次数加1,此时需要构建新的滑窗,再去操作right指针;重复这个过程直至遍历完S。如果left处的字符不是T中的字符的话,继续右移,知道遇到T中的字符。

实现代码如下:

class Solution {
public:
    string minWindow(string S, string T) {
        int s_len = S.size();
        int t_len = T.size();
        if(s_len == 0 || t_len == 0 || s_len < t_len)//特殊输入的处理
            return "";
        int cnt = 0;//用于统计当前滑动窗口内中包含T中的字符的个数
        int len = INT_MAX;//此处我用的int的最小值,也可以用s_len + 1
        int head = -1;//记录最终结果的初始位置
        int left = 0, right = 0;//定义滑动窗口的两个边界
        unordered_map<char,int> _map;//存储T中的字符及出现的次数
        for(int i = 0; i < t_len; i++)//初始化哈希表_map
        {
            if(_map.find(T[i])  == _map.end())
                _map.insert(pair<char,int>(T[i],1));
            else
                (_map.find(T[i])->second)++;
        }
        for(right = 0 ; right < s_len; right++)//移动右指针
        {
            if(_map.find(S[right]) != _map.end())//当窗口内部含有T中的字符时
            {
                _map[S[right]]--;//相应字符的此处减1
                if(_map[S[right]] >= 0)//减1后如果次数大于等于0,说明我们已经满足一个字符了,所以cnt加1
                    cnt++;
                while(cnt == t_len)//如果滑窗内已经包含所有T中的字符
                {
                    if(right - left + 1 < len)//更新最小的情况
                    {
                        len = right - left + 1;
                        head = left;
                    }
                    if(_map.find(S[left]) != _map.end())//如果左指针处指的是T中的字符;如果不是T中的字符则继续左移
                    {
                        _map[S[left]]++;//相应处的字符的次数加1
                       if(_map[S[left]] > 0)//如果加1之后此处大于0了,说明需要这个字符(这个字符是唯一的),如果向右移动的话(舍去这个字符),cnt就会减1
                            cnt--;
                        //如果这个字符不是唯一的话,不需要操作
                    }
                    left++;//移动左指针,直到滑窗中不再包含T中的全部字符
                }
            }
        }
        return head == - 1 ?"" : S.substr(head,len);//head== -1相当于没找到符合条件的子序列

    }
};

  

实际上,左指针移动时,相当于考虑了三种情况:left处的字符不是T中的字符、left处的字符是T中的字符且是唯一的、left处的字符是T中的字符但不是唯一的(滑窗中其他处还有这个字符,此时_map[S[left]]是小于等于0的)。

我们也可以把_map[S[i]]理解成还需要多少个S[i]字符。该算法的时间复杂度为O(N),空间复杂度为O(1),因为ASCII码的话,最多256个字符就行。

posted on 2018-07-18 17:35  Excaliburer  阅读(327)  评论(0编辑  收藏  举报