持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
给你一个字符串 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 次。
前缀和 + 状态压缩
我们对每个元音字母维护一个前缀和,定义 表示在字符串前 个字符中,第 个元音字母一共出现的次数。假设我们需要求出 这个区间的子串是否满足条件,那么我们可以用 ,在 的时间得到第 个元音字母出现的次数。对于每一个元音字母,我们都判断一下其是否出现偶数次即可。
我们利用前缀和优化了统计子串的时间复杂度,然而枚举所有子串的复杂度仍需要 ,不足以通过本题,还需要继续进行优化,避免枚举所有子串。我们考虑枚举字符串的每个位置 iii ,计算以它结尾的满足条件的最长字符串长度。其实我们要做的就是快速找到最小的 ,满足 (即每一个元音字母出现的次数)均为偶数,那么以 iii 结尾的最长字符串 长度就是 。
偶数这个条件其实告诉了我们,对于满足条件的子串而言,两个前缀和 和 的奇偶性一定是相同的,因为小学数学的知识告诉我们:奇数减奇数等于偶数,偶数减偶数等于偶数。因此我们可以对前缀和稍作修改,从维护元音字母出现的次数改作维护元音字母出现次数的奇偶性。这样我们只要实时维护每个元音字母出现的奇偶性,那么 满足条件当且仅当对于所有的 , 和 的奇偶性都相等,此时我们就可以利用哈希表存储每一种奇偶性(即考虑所有的元音字母)对应最早出现的位置,边遍历边更新答案。
var findTheLongestSubstring = function(s) {
const n = s.length;
const pos = new Array(1 << 5).fill(-1);
let ans = 0, status = 0;
pos[0] = 0;
for (let i = 0; i < n; ++i) {
const 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]) {
ans = Math.max(ans, i + 1 - pos[status]);
} else {
pos[status] = i + 1;
}
}
return ans;
};