LeetCode 395. 至少有K个重复字符的最长子串( 前缀和+分治 、枚举+滑动窗口)

至少有K个重复字符的最长子串

class Solution {
public:
    vector<int> pre[26];
    int len, k;
    string s;
    int longestSubstring(string s, int k) {
        len = s.size();
        this->k = k; 
        this->s = s;
        for(int i=0;i<26;i++) pre[i] = vector<int>(len,0);
        for(int i=0;i<len;i++){
            pre[s[i]-'a'][i] = 1;
        }
        for(int i=0;i<26;i++){
            for(int j=1;j<len;j++){
                pre[i][j] += pre[i][j-1];
            }
        }
        return dfs(0,len-1);
    }

    int dfs(int l,int r){
        if(l>r) return 0;
        int res = 0;
        int minCount = 1e9;
        char c;
        for(int i=0;i<26;i++){
            int cnt = l==0?pre[i][r]:pre[i][r] - pre[i][l-1];
            if(cnt < minCount && cnt != 0){
                minCount = cnt;
                c = i + 'a';
            }
        }
        
        if(minCount >= k) return r-l+1;

        for(int i=l;i<=r;i++){
            if(s[i] == c){
                continue;
            }else{
                int j = i;
                while(j<=r && s[j] != c){
                    j++;
                }
                res = max(res,dfs(i,j-1));
                i = j;
            }
        }
        return res;
    }
};

奇妙的滑动窗口:

class Solution {
public:
    int longestSubstring(string s, int k) {
        int cnt[26] = {0};
        int n = s.size();
        int ans = 0;
        for(int t = 1;t <= 26;t++){
            memset(cnt,0,sizeof cnt);
            int l = 0 ,r = 0;
            int tot = 0;
            while(r<n){
                if(cnt[s[r]-'a']++ == 0){
                    tot++;
                }
                r++;
                while(tot > t){
                    if(--cnt[s[l]-'a'] == 0){
                        tot--;
                    }
                    l++;
                }
                bool f = true;
                for(int i=0;i<26;i++){
                    if(cnt[i]>0 && cnt[i] < k) f = false;
                }
                if(f) ans = max(ans,r-l);
            }
        }
        return ans;
    }
};

时间复杂度: O ( l e n ∗ C 2 ) O(len*C^2) O(lenC2)

此外还剩下一个细节:如何判断某个子串内的字符是否都出现了至少 k 次?我们当然可以每次遍历 \textit{cnt}cnt 数组,但是这会带来 O(C) 的额外开销。
我们可以维护一个计数器 less,代表当前出现次数小于 k (且不为 0)的字符的数量。每次移动滑动窗口的边界,只会让某个字符的出现次数加一或者减一。对于移动右边界的情况而言:
当某个字符的出现次数从 0 增加到 1 时,将 less 加一;
当某个字符的出现次数从 k-1 增加到 k 时,将 less 减一。
对于移动左边界的情形,讨论是类似的。

这个技巧已经使用过很多次了。

class Solution {
public:
    int longestSubstring(string s, int k) {
        int cnt[26] = {0};
        int n = s.size();
        int ans = 0;
        for(int t = 1;t <= 26;t++){
            memset(cnt,0,sizeof cnt);
            int l = 0 ,r = 0;
            int tot = 0, less = 0;
            
            while(r<n){
                if(cnt[s[r]-'a']++ == 0){
                    tot++;
                    less++;
                }
                if(cnt[s[r]-'a'] == k){
                    less--;
                }
                
                r++;
                while(tot > t){
                    cnt[s[l] - 'a']--;
                    if (cnt[s[l] - 'a'] == k - 1) {
                        less++;
                    }
                    if (cnt[s[l] - 'a'] == 0) {
                        tot--;
                        less--;
                    }
                    l++;
                }
                if(less == 0) ans = max(ans,r-l);
            }
        }
        return ans;
    }
};

时间复杂度: O ( l e n ∗ C ) O(len*C) O(lenC)

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页