LeetCode 76:最小覆盖子串(困难)

LeetCode 76:最小覆盖子串(困难)
在这里插入图片描述
在这里插入图片描述

解答

基本的滑动窗口应该不难想到,关键是如何判断s的滑动窗口中包含t的所有字母。

代码一

对s的滑动窗口和字符串t统计字母频率,记录在scnt和tcnt中,对于每个位置i的字母, scnt[i] ≥ \ge tcnt[i], 否则s的滑动窗口不能完全包含字符串t。由此得到函数judge


class Solution {
public:
    bool judge(int* scnt, int* tcnt, string t){
        for(int i=0; i<t.size(); i++){
            if(scnt[ t[i] ] < tcnt[ t[i] ]) return false;
        }
        return true;
    }
    string minWindow(string s, string t) {
        int left=0, ans=s.size()+1, start=0, len=0;
        int scnt[128] = {0}, tcnt[128] = {0};
        for(int i=0; i<t.size(); i++){
            tcnt[ t[i] ]++;
        }

        for(int i=0; i<s.size(); i++){
            scnt[ s[i] ]++;
            if(tcnt[ s[i] ] == 0) continue;
            while(judge(scnt, tcnt, t)){
                len = i-left+1;
                if(ans > len){
                    ans = len;
                    start=left;
                }
                scnt[ s[left++] ]--;
            }
        }
        return ans==s.size()+1 ? s.substr(start, ans) : "";
    }
};

上面的代码思路很清晰,可惜TLE
在这里插入图片描述
在后面写完代码二后,回头再看代码一,查看265测试用例,发现字符串t是很长的,中间judge函数的复杂度为O(t.size()),需要对judge进行优化。
其实本来对judge优化没有太大的把握,只是尽量减少265测试用例的复杂度,将原来需要循环t.size()次减少为26*2次(有的样例中同时有大小写),只判断ASCII中英文字母

    bool judge(int* scnt, int* tcnt, string t){    
        for(int i='A'; i<='Z'; i++){
            if(scnt[ i ] < tcnt[ i ]) return false;
        }
        for(int i='a'; i<='z'; i++){
            if(scnt[ i ] < tcnt[ i ]) return false;
        }
        return true;
    }

在这里插入图片描述
勉强通过

代码二:

同样是滑动窗口的思想,同样是对judge函数进行优化。将judge过程从O(t.size())降低到了O(1)的复杂度。
在每一次滑动窗口移动的过程中,judge函数都对scnt和tcnt整个进行判断,但是每一次滑动窗口移动的过程中大部分元素没有变,每次只变化了一个
在这里插入图片描述
右边界向右滑动时

  • 要么winFreq[s[right]] >= tFreq[s[right]], 此时s[right]在字符串t中没有出现(tFreq[s[right]]=0) 或是s[right]在s中已经够了(winFreq[s[right]] >= tFreq[s[right]]),
  • 要么winFreq[s[right]] < tFreq[s[right]],s[right]在s中不够

核心部分是dis==t.size(),相当于judge函数,表示滑动窗口内部包含了t中的所有字符

class Solution {
public:
    string minWindow(string s, string t){
        int left=0, start=0, len=0, ans=s.size()+1;
        int scnt[128]={0}, tcnt[128]={0};
        for(int i=0; i<t.size(); i++)
            tcnt[ t[i] ]++;

        // dis表示滑动窗口内部包含了t中字符的个数
        int dis=0;
        
        for(int i=0; i<s.size(); i++){
            // s[i]不在t中
            if( tcnt[ s[i] ] == 0 ) continue;

            // 否则s[i]是t中的字符, 且s[i]在滑动窗口中不够,dis++
            if( scnt[ s[i] ]++ < tcnt[ s[i] ] ) dis++;

            // 滑动窗口中已经全部包含了t的字母(dis==t.size())
            while( dis == t.size() ){
                len = i-left+1;
                if(ans > len){
                    ans = len;
                    start = left;
                }

                // 左边界的字母是t中的字母,left要向右移动,该元素被丢掉
                if( tcnt[s[left]] != 0 ){
                    if( scnt[s[left]]-- <= tcnt[s[left]] ) dis--;
                }
                left++;
            }

        }
        return ans==s.size()+1 ? "" : s.substr(start, ans);

    }
};

在这里插入图片描述

类似题目

438 找到字符串中所有字母异位词(中等)
这个题目的滑动窗口大小固定,同样使用了dis的概念

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值