Minimum Window Substring

一. Minimum Window Substring

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

For example,

S = “ADOBECODEBANC”
T = “ABC”
Minimum window is “BANC”.

Note:
If there is no such window in S that covers all characters in T, return the empty string “”.

If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

Difficulty:Hard

TIME:40MIN

解法

这道题挺有意思,要求包含子串的最小窗口。如果子串字符不重复,那当然很简单,只需要不断更新每一个字符的位置,然后记录最大下标的字符和最小下标的字符之前下标的差值就可以了。

如果子串字符可以重复,也可以采用类似的方法,比较直观的做法就是采用双端队列,如果字符数超过允许出现的字符数,就从队列中弹出首元素,然后插入当前下标。

string minWindow(string s, string t) {
    vector<int> m(128, 0); //采用vector记录字符比map要快,反正字符值也不会超过128
    vector<deque<int>> d(128);  //每一个字符维护一个自己的双端队列
    set<char> u(t.begin(), t.end()); //为了查找方便使用一个set
    for(int i = 0; i < t.size(); i++)
        m[t[i]]++;
    int num = 0;
    int len = s.size();
    string result = "";
    for(int i = 0; i < s.size(); i++) {
        if(m[s[i]] != 0) {
            d[s[i]].push_back(i);
            if(d[s[i]].size() > m[s[i]])
                d[s[i]].pop_front(); //弹出头元素
            else
                num++; //记录出现了多少个子串中的字符
            if(num == t.size()) {
                int left = INT32_MAX,right = -1;
                /*更新left和right的值*/
                for(auto it = u.begin(); it != u.end(); it++) {
                    if(d[*it].front() < left)
                        left = d[*it].front();
                    if(d[*it].back() > right)
                        right = d[*it].back();
                }
                if(len >= right - left + 1) {
                    len = right - left + 1;
                    result = s.substr(left, len);
                }
            }
        }
    }
    return result;
}

代码的时间复杂度为 O(n)

优化

写完代码之后才发现,我们只需要知道最左边的字符下标left和最右边的字符下标right就可以了,而之所以采用双端队列,就是因为如果弹出的恰好是left下标,那么我们应该重新在队列中找到一个新的left下标来代替。

不过,我们可以利用map本身来帮我们完成这样的工作,这种解法的思路采用一个例子来描述就是:

  • 比如对于字符s为acbeba,字符串t为ab,num为出现的有效字符数目
  • 根据t来初始化map
  • 这个时候acbeba的map对应值为101011
  • 遍历下标0,这个时候m[a]–,num++,left为0,map对应值变为001010
  • 遍历下标1,这个时候m[c]–,map对应的值变为0-11010
  • 遍历下标2,这个时候m[b]–,num++,map对应值变为0-10000,num为2,说明找到一个窗口,首先让left变得有效,计算第一个窗口为acb,计算完成后,舍弃left,并让left对应的字符在map中的值加一,因此left为1,map对应的值变为1-10001(这一步是这个解法的关键所在,因为必须舍弃left,才能让窗口变得更小
  • 遍历下标3,这个时候m[e]–,map对应值变为1-10-101
  • 遍历下标4,这个时候m[b]–,map对应值变为1-1-1-1-11
  • 遍历下标5,这个时候m[a]–,num++,map对应的值变为0-1-1-1-10,num为2,说明找到一个窗口,首先让left变得有效,其余操作和遍历下标2的时候相同。
string minWindow(string s, string t) {
    vector<int> m(128, 0); //这个m表示我需要的字符大于0,不需要的字符等于0
    for(int i = 0; i < t.size(); i++)
        m[t[i]]++;
    int num = 0;
    int len = s.size();
    string result = "";
    int left = 0;
    for(int i = 0; i < s.size(); i++) {
        if(m[s[i]] > 0) //必须得找到需要的字符,num才加一
            num++;
        m[s[i]]--;
        if(num == t.size()) {
            while(m[s[left]] < 0) { //这一步是为了找到有效的left,有效的left肯定有m[s[left]] == 0
                m[s[left]]++;
                left++;
            }
            if(len >= i - left + 1) {
                len = i - left + 1;
                result = s.substr(left, len);
            }
            /*舍弃left,num的值减一,现在我们需要寻找到另一个s[left]*/
            m[s[left]]++;
            left++;
            num--;
        }
    }
    return result;
}

代码的时间复杂度为 O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值