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