1371. 每个元音包含偶数次的最长子字符串 - 力扣(LeetCode)
给你一个字符串 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,以保存不包含的状态
题目中可以使用 pre[i][5]
来存储 5 个元音的数量
pre[i][k] - pre[j][k]
判断元音数量是否相等,并且可以统计长度
状态压缩
只使用前缀和,不可避免地依旧需要遍历 O(n^2) 复杂度
要求统计偶数,可以使用数的奇偶性来进行统计
奇数 - 奇数 = 偶数
偶数 - 偶数 = 偶数
用 0 代表偶数
用 1 代表奇数
当各个元音的奇偶性相同时,两个位置的前缀和相减后元音的偶数个数就必定是 偶数
使用二进制数表示各元音个数的奇偶性
if(s.charAt(i)=='a'){
cur = cur ^ (1<<0);
}else if(s.charAt(i)=='e'){
cur = cur ^ (1<<1);
}else if(s.charAt(i)=='i'){
cur = cur ^ (1<<2);
}else if(s.charAt(i)=='o'){
cur = cur ^ (1<<3);
}else if(s.charAt(i)=='u'){
cur = cur ^ (1<<4);
}
1 << 0 表示二进制数 1 左移 0 位,结果就是 1 (二进制为 00001)。
1 << 1 表示二进制数 1 左移 1 位,结果就是 2 (二进制为 00010)。
1 << 2 表示二进制数 1 左移 2 位,结果就是 4 (二进制为 00100)。
1 << 3 表示二进制数 1 左移 3 位,结果就是 8 (二进制为 01000)。
1 << 4 表示二进制数 1 左移 4 位,结果就是 16 (二进制为 10000)。
压缩后可以使用 异或运算 进行判断
如果新位置的字符是元音,则对对应的位置进行异或,而不需要统计数量
pre[j] = {0 0 1 0 0}
新位置为 i
pre[j+1] = pre[j] ^ 00100 = 00000
在记录奇偶性的同时,查找之前是否出现过相同奇偶性的状态
记录最早出现该状态的位置,就可以在遍历统计时,同时完成以当前下标为后缀的最长偶数元音个数字符串长度的计算
- 不存在当前状态,记录当前状态
- 已存在当前状态,统计长度
使用 map 记录
key 为压缩状态表
value 为最早出现的位置
也可以使用数组记录
5 个元音,对应长度为 1111
32 长度的数组
对应的状态下标存储最早出现的位置
前缀表的下标问题
前缀表需要记录空的情况,占用了 0
1371. 每个元音包含偶数次的最长子字符串
使用了前缀表,初始为 0 的情况记录了下标 0
在后续记录时,就需要对 i + 1
可以不使用 0 下标,而是使用 -1 记录初始 0 的情况
那后续记录时就不需要 + 1
参考 LeetCode - 1371 每个元音包含偶数次的最长子字符串(Java & JS & Python & C)_leetcode 1371-CSDN博客
题解
使用长度为 2^5 长度记录所有可能的奇偶状态
0 表示 a
1 表示 e
2 表示 i
3 表示 o
4 表示 u
00001 十进制 1 表示 a 有奇数个
class Solution {
public int findTheLongestSubstring(String s) {
int n = s.length();
// 状态表
// 记录出现位置
int[] map = new int[1<<5]; // 1 左移5, 代表11111种状态
Arrays.fill(map, -1); // 填充 -1,区分 00000
map[0] = 0; // 初始状态,不包含任何元音
int cur = 0; // 初始状态,用于推导
int maxResult = 0;
for (int i=0; i<n; i++){
if(s.charAt(i)=='a'){
cur = cur ^ (1<<0);
}else if(s.charAt(i)=='e'){
cur = cur ^ (1<<1);
}else if(s.charAt(i)=='i'){
cur = cur ^ (1<<2);
}else if(s.charAt(i)=='o'){
cur = cur ^ (1<<3);
}else if(s.charAt(i)=='u'){
cur = cur ^ (1<<4);
}
if(map[cur]!=-1){
// 已存在
// 比较长度
maxResult = Math.max(maxResult, i - map[cur] + 1);
}else{
// 不存在,记录
map[cur] = i+1;
}
}
return maxResult;
}
}