Medium
题目描述
给你一个字符串 s,请你返回满足以下条件的最长子字符串的长度:每个元音字母(‘a’, ’e’, ‘i’, ‘o’, ‘u’)在子字符串中都出现偶数次。
示例 1:
输入: s = "eleetminicoworoep"
输出: 13
解释: 最长子字符串是 "leetminicowor",它包含 e, i, o 各 2 个,a, u 各 0 个。
示例 2:
输入: s = "leetcodeisgreat"
输出: 5
解释: 最长子字符串是 "leetc",它包含 2 个 e。
示例 3:
输入: s = "bcbcbc"
输出: 6
解释: 在这种情况下,给定的字符串 "bcbcbc" 就是最长的,因为所有元音 a, e, i, o, u 都出现 0 次。
约束条件:
1 <= s.length <= 5 x 10^5s只包含小写英文字母。
提示:
- 用位掩码表示元音字母的奇偶性
- 预计算元音字母位掩码的前缀异或,然后获取最长有效子字符串
解题思路
这道题的核心思路是使用状态压缩和前缀异或的技巧。
关键观察:
- 我们只关心每个元音字母出现的次数是奇数还是偶数,而不是具体次数
- 可以用一个5位的二进制数(位掩码)来表示5个元音字母的奇偶状态
- 当两个位置的状态相同时,它们之间的子字符串中所有元音都出现偶数次
算法步骤:
- 使用位掩码
state记录当前位置各元音字母的奇偶性,初始为0(全为偶数) - 遍历字符串,遇到元音字母就将对应位翻转(异或操作)
- 使用哈希表记录每个状态第一次出现的位置
- 当遇到相同状态时,计算当前位置与该状态第一次出现位置之间的距离
- 维护最长距离作为答案
为什么这样有效? 如果位置 i 和位置 j 的状态相同,说明从 i+1 到 j 这段区间内,每个元音字母出现的次数都是偶数次(因为相同状态被"抵消"了)。
时间复杂度:O(n),空间复杂度:O(1)(因为状态数最多32种)
代码实现
class Solution {
public:
int findTheLongestSubstring(string s) {
unordered_map<int, int> stateMap;
stateMap[0] = -1; // 初始状态,所有元音都是偶数次
int state = 0;
int maxLen = 0;
for (int i = 0; i < s.length(); i++) {
char c = s[i];
// 更新状态
if (c == 'a') state ^= 1;
else if (c == 'e') state ^= 2;
else if (c == 'i') state ^= 4;
else if (c == 'o') state ^= 8;
else if (c == 'u') state ^= 16;
if (stateMap.count(state)) {
maxLen = max(maxLen, i - stateMap[state]);
} else {
stateMap[state] = i;
}
}
return maxLen;
}
};
class Solution:
def findTheLongestSubstring(self, s: str) -> int:
state_map = {0: -1} # 初始状态,所有元音都是偶数次
vowels = {'a': 1, 'e': 2, 'i': 4, 'o': 8, 'u': 16}
state = 0
max_len = 0
for i, c in enumerate(s):
if c in vowels:
state ^= vowels[c]
if state in state_map:
max_len = max(max_len, i - state_map[state])
else:
state_map[state] = i
return max_len
public class Solution {
public int FindTheLongestSubstring(string s) {
Dictionary<int, int> stateMap = new Dictionary<int, int>();
stateMap[0] = -1; // 初始状态,所有元音都是偶数次
int state = 0;
int maxLen = 0;
for (int i = 0; i < s.Length; i++) {
char c = s[i];
// 更新状态
switch (c) {
case 'a': state ^= 1; break;
case 'e': state ^= 2; break;
case 'i': state ^= 4; break;
case 'o': state ^= 8; break;
case 'u': state ^= 16; break;
}
if (stateMap.ContainsKey(state)) {
maxLen = Math.Max(maxLen, i - stateMap[state]);
} else {
stateMap[state] = i;
}
}
return maxLen;
}
}
var findTheLongestSubstring = function(s) {
const stateMap = new Map();
stateMap.set(0, -1); // 初始状态,所有元音都是偶数次
const vowels = new Map([['a', 1], ['e', 2], ['i', 4], ['o', 8], ['u', 16]]);
let state = 0;
let maxLen = 0;
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (vowels.has(c)) {
state ^= vowels.get(c);
}
if (stateMap.has(state)) {
maxLen = Math.max(maxLen, i - stateMap.get(state));
} else {
stateMap.set(state, i);
}
}
return maxLen;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历字符串一次,每次操作都是常数时间 |
| 空间复杂度 | O(1) | 哈希表最多存储32种不同状态(2^5),为常数空间 |