题目描述
提示:
- 1 <= s.length <= 5 x 10^5
- s 只包含小写英文字母。
思路描述:
首先题目中要求子字符串中每个元音字母恰好出现偶数次,我们就可以使用0
和 1
来标识每个字母的状态(偶数次或奇数次
),我们不需要知道每个字母出现的完整次数,只需要知道这个次数的奇偶性
那么我们可以注意到奇数次+ 1 = 偶数次,偶数次 + 1 = 奇数次
,所以我们可以使用 异或 来参与运算: 比如 aba
初始时 status = 00000
,然后到 a
的时候00000 ^ 00001 = 00001
,1
说明a
出现奇数次
然后到b
的时候 00001 ^ 00010 = 00011
,两个 1
说明 a
、b
都出现奇数次
最后到 a
的时候 00011 ^ 00001 = 00010
,说明只有b
出现奇数次了。
以上也说明我们确实是可以使用状态码去标识每个元音字母出现次数的奇偶性。
那么我们怎么去统计最长子串的长度呢?
首先我们先盘盘哪些子串符合要求,因为现在每个下标对应的状态码其实也就只有0
和 1
如果坐标 i 对应的状态码是 00011
,坐标 j
对应的状态码是 00011
,那么他们俩中间的元音字母数一定是偶数,如果某一位不相同,那么绝对不可能是偶数,因为偶数-奇数=奇数,奇数-偶数=奇数
所以我们每次求出一个坐标的状态码的时候就去瞅瞅这个状态码前面是否存在,如果存在,那么就计算一下之间子字符串的长度就 ok 了,那么我们还需要啥?明显需要一个hash表,存储每个状态码对应的下标!当然因为我们状态码最长也就是 11111 = 2^5 - 1 = 31
,开一个 32 大小的数组就好了。
int findTheLongestSubstring(string s) {
int ans = 0, status = 0, n = s.size();
vector<int> pos(32, -1); // 五个元音字母最多有32个状态 -1可以使得初始时每个bit位都为1
pos[0] = 0;
for (int i = 0; i < n; i ++) {
if (s[i] == 'a') {
status ^= 1<<0;
} else if (s[i] == 'e') {
status ^= 1<<1;
} else if (s[i] == 'i') {
status ^= 1<<2;
} else if (s[i] == 'o') {
status ^= 1<<3;
} else if (s[i] == 'u') {
status ^= 1<<4;
}
if (pos[status] != -1) {
ans = max(ans, i + 1 - pos[status]);
} else {
pos[status] = i + 1;
}
}
return ans;
}
复杂度分析
时间复杂度:O(n),其中 n 为字符串 s 的长度。我们只需要遍历一遍字符串即可求得答案,因此时间复杂度为 O(n)。
空间复杂度:O(S),其中 S 表示元音字母压缩成一个状态数的最大值,在本题中 S = 32。我们需要对应 S 大小的空间来存放每个状态第一次出现的位置,因此需要 O(S) 的空间复杂度。
引用自力扣优质题解@yizh