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

例如: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个不同的字符,我们就可以针对窗口内只能出现的不同字符个数进行筛选。i和start分别表示滑动窗口的左边界和右边界,用数组charCnt记录不同字符出现的次数,unique表示不同的字符个数,若当前窗口内出现的不同字符超过规定的,就缩小窗口的范围,将左边界往右移动,最后检测窗口内的不同字符出现的次数是否都大于或等于k,若满足,更新最大值;时间复杂度为O(n),因为第一层是个常数,不影响时间复杂度
class Solution {
public:
int longestSubstring(string s, int k) {
int res = 0, n = s.length();
for (int cnt = 1; cnt <= 26; ++cnt){ //窗口内只有可能出现26个不同的字符
int start = 0, i = 0, unique = 0;
vector<int> charCnt(26, 0);
while (i < n){
bool valid = true;
if (charCnt[s[i++]-'a']++ == 0) ++unique; //记录字符出现的次数和不同字符的个数
while (unique > cnt){ //若当前窗口内的不同字符超过规定的cnt,则要缩小窗口
if (--charCnt[s[start++]-'a'] == 0)
--unique;
}
for (int c_cnt : charCnt){ //检查窗口内不同字符出现的个数是否大于或等于k
if (0 < c_cnt && c_cnt < k){
valid = false;
break;
}
}
if (valid) res = max(res, i - start);
}
}
return res;
}
};
来源:CSDN
作者:Czy_whlg
链接:https://blog.csdn.net/Czyaun/article/details/103510105