Given a string s
consisting only of lowercase English letters, find the length of the longest substring where each of the vowels ('a'
, 'e'
, 'i'
, 'o'
, 'u'
) appears an even number of times (including zero times).
s
contains only lowercase letters.At first glance, you might think of checking every possible substring and counting the vowels, but this brute-force approach would be too slow for large strings. Instead, we need a way to efficiently track the counts of each vowel as we scan through the string.
The key insight is that the parity (even or odd) of the count for each vowel is all that matters, not the actual count. If we can represent the state of vowel parities at each position, we can quickly check if a substring has all vowels in even counts by comparing states.
This leads to the idea of using a bitmask to encode the even/odd status of each vowel. We can then use a hash map to record the first occurrence of each state, allowing us to find the longest substring with matching start and end states in O(1) time per character.
Here’s a step-by-step breakdown of the optimized solution:
a
, e
, i
, o
, u
.a
and i
have appeared an odd number of times, the bitmask is 10100
(in binary).This approach ensures that we only need a single pass through the string, with O(1) work per character.
Let's use the input s = "eleetminicoworoep"
.
By representing the parity of vowel counts as a bitmask and tracking their first occurrences, we can efficiently find the longest substring where all vowels appear an even number of times. The approach leverages bitwise operations and hash maps for O(1) state updates and lookups, making it both elegant and highly efficient. This problem is a great example of how encoding state and using prefix information can dramatically simplify and speed up substring problems.
class Solution:
def findTheLongestSubstring(self, s: str) -> int:
vowel_to_bit = {'a':0, 'e':1, 'i':2, 'o':3, 'u':4}
state = 0
seen = {0: -1}
max_len = 0
for i, c in enumerate(s):
if c in vowel_to_bit:
state ^= (1 << vowel_to_bit[c])
if state in seen:
max_len = max(max_len, i - seen[state])
else:
seen[state] = i
return max_len
class Solution {
public:
int findTheLongestSubstring(string s) {
unordered_map<int, int> seen;
seen[0] = -1;
int state = 0, maxLen = 0;
for (int i = 0; i < s.size(); ++i) {
char c = s[i];
if (c == 'a') state ^= (1 << 0);
else if (c == 'e') state ^= (1 << 1);
else if (c == 'i') state ^= (1 << 2);
else if (c == 'o') state ^= (1 << 3);
else if (c == 'u') state ^= (1 << 4);
if (seen.count(state)) {
maxLen = max(maxLen, i - seen[state]);
} else {
seen[state] = i;
}
}
return maxLen;
}
};
class Solution {
public int findTheLongestSubstring(String s) {
Map<Integer, Integer> seen = new HashMap<>();
seen.put(0, -1);
int state = 0, maxLen = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == 'a') state ^= (1 << 0);
else if (c == 'e') state ^= (1 << 1);
else if (c == 'i') state ^= (1 << 2);
else if (c == 'o') state ^= (1 << 3);
else if (c == 'u') state ^= (1 << 4);
if (seen.containsKey(state)) {
maxLen = Math.max(maxLen, i - seen.get(state));
} else {
seen.put(state, i);
}
}
return maxLen;
}
}
var findTheLongestSubstring = function(s) {
const vowelToBit = {'a':0, 'e':1, 'i':2, 'o':3, 'u':4};
let state = 0;
let seen = {0: -1};
let maxLen = 0;
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (vowelToBit.hasOwnProperty(c)) {
state ^= (1 << vowelToBit[c]);
}
if (seen.hasOwnProperty(state)) {
maxLen = Math.max(maxLen, i - seen[state]);
} else {
seen[state] = i;
}
}
return maxLen;
};