题目描述
【leetcode】5337. 每个元音包含偶数次的最长子字符串( Find the Longest Substring Containing Vowels in Even Counts)
给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = “eleetminicoworoep”
输出:13
解释:最长子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = “leetcodeisgreat”
输出:5
解释:最长子字符串是 “leetc” ,其中包含 2 个 e 。
示例 3:
输入:s = “bcbcbc”
输出:6
解释:这个示例中,字符串 “bcbcbc” 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。
提示:
- 1 <= s.length <= 5 x 10^5
- s 只包含小写英文字母。
第一次解答
思路
暴力法。滑动窗口,窗口长度分别设置为1,2,3,…
毫无疑问最后超时了,所以也不确定代码对不对。
代码:
class Solution {
pu
blic:
bool isEven(unsigned long *count){
return (count['a'] % 2 == 0 && count['e'] % 2 == 0 &&
count['i'] % 2 == 0 && count['o'] % 2 == 0 &&
count['u'] % 2 == 0
);
}
int findTheLongestSubstring(string s) {
int max_length = 0;
if(s.size() == 0)
return 0;
// 滑动窗口
int window_length = s.size();
while(window_length > 0){
int window_start = 0;
// 怕麻烦,直接把asscii码都申请了
unsigned long count[128] = {0};
memset(&count[0], 0, 128*sizeof(unsigned long));
for(int window_end=0; window_end<s.size(); window_end++){
count[s[window_end]] += 1;
if(window_end - window_start + 1 >= window_length){
bool is_even = isEven(&count[0]);
count[s[window_start]] -= 1;
window_start++;
if(!is_even){
continue;
}
if(window_length > max_length){
max_length = window_length;
break;// 在该长度下找到了,就遍历更大的长度
}
}
}
window_length--;
}
return max_length;
}
};
结果:
超时。
第二次解答
思路
看了题解。
我们知道,一个数加上偶数不改变奇偶性,例如奇数+偶数=奇数,偶数+偶数=偶数。
题目要求是5个元音字母出现偶数次的最长区间
,为了便于说明,我们先仅考虑字母a出现偶数次数的最长区间
,我们假设这个满足条件的区间为[left, right]。[0, left]与[left, right]可组成区间[0, right],记这三个区间字母a个数分别为L、K和R。
其中K为偶数时,[left, right]为我们要找的区间。根据上面所说的,偶数不改变奇偶性,所以K不改变L和R的奇偶性,因此L和R的奇偶性相同。
反过来说,当L和R的奇偶性相同时,K就为偶数,(left, right]就是我们要找的区间,right - left就是区间长度。
以上结论是仅考虑字母a出现偶数次数的最长区间
得到的,当问题扩展为5个元音字母出现偶数次的最长区间
也有类似结论。
回到本题,考虑5个元音字母的奇偶性时,存在2^5=32种奇偶性状态(只用到了5bits,每个bit表示一个元音字母出现的奇偶性,0表示对应字母出现偶数次,1代表对应字母出现奇数次),我们记录每一种状态第一次出现的序号,相当于记录left,在后续过程中若出现相同状态则表明遇到了区间(left, right],计算此时的区间长度,若大于最大长度,则更新最大长度。
注意:本题能用这种方法的前提是一个数加上偶数不改变奇偶性
,所以当题目改为5个元音字母出现奇数次的最长区间
时,这种方法就不适用了。但是我们可以转换一下题目,使得方法适用于题目,我们可以把题目看为除了5个元音字母外的字母,出现偶数次的最长区间
,这样,就还可以使用本文的方法。
test case:
“eleetminicoworoep”
“leetcodeisgreat”
“bcbcbc”
代码:
注意:这里的curr_state默认为0,因为这时候确实没有任何元音字母,这个状态第一次的序号应为-1。若不这样做,计算(left, right]半开半闭区间的长度right-left时,当这个区间是在默认状态下找到时,会出错。出错的测试用例为:“leetcodeisgreat”
class Solution {
public:
int findTheLongestSubstring(string s) {
vector<int> status(1<<5, -2);// 默认所有状态的left都为-2,表示还没遇到第一次出现的序号
int curr_state = 0; // 默认状态为全0,5bits,每个bit表示一个元音字母出现的奇偶性,0表示偶数次
status[curr_state] = -1;// 注意:默认状态出现的序号为-1处。
int max_length = 0;
for(int i=0; i<s.size(); ++i){
// 计算当前状态
switch(s[i]){
case 'a':curr_state ^= 1<<0;break;
case 'e':curr_state ^= 1<<1;break;
case 'i':curr_state ^= 1<<2;break;
case 'o':curr_state ^= 1<<3;break;
case 'u':curr_state ^= 1<<4;break;
default:break;
}
// 若状态第一次出现,记录出现的序号
if(-2 == status[curr_state]){
status[curr_state] = i;
}
// 否则,表示再次遇到同状态,找到了区间(left, right],更新最大长度
else{
max_length = max(max_length, i - status[curr_state]);
}
}
return max_length;
}
};
结果: