Longest Substring with At Least K Repeating Characters

 1. 解析

题目大意,求解最长的子序列,该子序列中不同字符出现的次数不少于k次。

 2. 分析

按照我们正常的思路就是无非就是检测每个字符字串的组合,但存在一个问题是如果如何避免每次从开头的下一个字符开始遍历。就是设置一个mask掩码,字符串只由26个小写字母组成,最多用一个26位的二进制掩码即可表示,另外用一个计数器,记录每个字符出现的次数,若在子串当中的不同字符出现的次数都大于或等于k,那么掩码就会被重置为0,若该子串的长度大于之前的最大长度,用该子串更新当前的最大值。并记录下当前的索引,这是关键,下一次就可以从该位置的下一个位置开始遍历,因为之前的状态已经检测过,防止每次都从头开始遍历。时间复杂度为O(n^{2})。

例如:s = "ababbc"   k = 2

'a', mask = 1    res = 0   max_index = 0     

'b', mask = 11  res = 0   max_index = 0   

'a', mask = 10  res = 0   max_index = 0    即若字串中的某个字符出现的次数大于或等于k,对应的mask位置就会被置为0

'b', mask = 00  res = 4   max_index = 3

'b', mask = 00  res = 5   max_index = 4

'c', mask = 100  res = 5   max_index = 4

class Solution {
public:
    int longestSubstring(string s, int k) {
        int res = 0;
        int i = 0, max_index = 0;
        int n = s.length();
        
        while (i < n){
            vector<int> count(26, 0);
            int mask = 0;  //mask掩码
            max_index = i; //关键
            for (int j = i; j < n; ++j){
                int t = s[j] - 'a';
                count[t]++;
                if (count[t] < k){ //字符掩码,可以标记出现过的字符
                    mask |= (1 << t);
                }
                else{
                    mask &= (~(1 << t));
                }
                if (!mask){ //若当前字串中不同字符出现的次数均大于或等于k,更新最大值
                    res = max(res, j - i + 1);
                    max_index = j;
                }
            }
            i = max_index + 1;
        }
        
        return res;
    }
};

3. 滑动窗口

参考@Grandyang的思路,由于字符串只由26个小写字母组成,即字串当中最多也就只有26个不同的字符,我们就可以针对窗口内只能出现的不同字符个数进行筛选。left和right分别表示滑动窗口的左边界和右边界,用数组nums记录不同字符出现的次数,diff表示不同的字符个数,若当前窗口内出现的不同字符超过窗口大小,就缩小窗口的范围,将左边界往右移动,最后检测窗口内的不同字符出现的次数是否都大于或等于k,若满足,更新最大值;时间复杂度介于O(n)和O(n^{2})之间,因为第一层是个常数,不影响时间复杂度。

class Solution {
public:
    int longestSubstring(string s, int k){
        int res = 0;
        for (int diffAlpha = 1; diffAlpha <= 26; ++diffAlpha){ //将不同字母的个数作为滑动窗口大小
            int left = 0, n = s.size(), diff = 0; //diff表示当前不同字母的个数 
            vector<int> nums(26, 0);
            for (int right = 0; right < n; ++right){
                if (nums[s[right]-'a']++ == 0) ++diff;
                if (diff < diffAlpha) continue;
                while (diff > diffAlpha){
                    if (--nums[s[left++]-'a'] == 0) //滑动左边窗口,若当前窗口内不存在该字符
                        --diff;
                }
                int repeatNum = count_if(nums.begin(), nums.end(), [k](int num){
                    return num >= k || num == 0;
                }); //统计不同字符出现次数大于或等于K的个数
                if (repeatNum == 26) //若该子串出现的字符个数均大于或等于k
                    res = max(res, right-left+1);
            }
        }
        
        return res;
    }
};

 [1]https://www.cnblogs.com/grandyang/p/5852352.html 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值