LeetCode——每个元音包含偶数次的最长子字符串
Q:给你一个字符串 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 次。
A:前缀和 + 状态压缩
首先题目中要求子字符串中每个元音字母恰好出现偶数次,我们就可以使用 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 大小的数组就好了。
public int findTheLongestSubstring(String s) {
int n = s.length();
int[] pos = new int[1 << 5];
Arrays.fill(pos, -1);
int ans = 0, status = 0;
pos[0] = 0;
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (ch == 'a') {
status ^= (1 << 0);
} else if (ch == 'e') {
status ^= (1 << 1);
} else if (ch == 'i') {
status ^= (1 << 2);
} else if (ch == 'o') {
status ^= (1 << 3);
} else if (ch == 'u') {
status ^= (1 << 4);
}
if (pos[status] >= 0) {
ans = Math.max(ans, i + 1 - pos[status]);//后面每次遇到这种状态我们就去求差,然后用一个中间变量去存储最大的那个差,即为题目所求。
} else {
pos[status] = i + 1;//这种状态是第一次出现的,我们把它的下标记录下来。
}
}
return ans;
}