Find the Longest Substring Having Vowels in Even Counts
Bitmask parity of vowel counts as state — first-occurrence map gives the longest range with all-zero parity.
4 min read
bit-manipulation prefix-state hash-map
Problem#
Return the length of the longest substring in which each of a, e, i, o, u appears an even number of times.
Examples#
"eleetminicoworoep"→13(substring"leetminicowor")."leetcodeisgreat"→5("leetc")."bcbcbc"→6.
Constraints#
1 <= s.length <= 5 * 10^5.
Approach#
Encode parity of each vowel as one bit of a 5-bit state. Walk the string maintaining the running XOR state; record the first index at which each state was seen. The longest substring ending at i with all-even parities is i - firstSeen[state].
Solution#
class Solution {public: int findTheLongestSubstring(string s) { unordered_map<char,int> bit = {{'a',0},{'e',1},{'i',2},{'o',3},{'u',4}}; vector<int> first(32, -2); first[0] = -1; int state = 0, ans = 0; for (int i = 0; i < (int)s.size(); ++i) { auto it = bit.find(s[i]); if (it != bit.end()) state ^= (1 << it->second); if (first[state] == -2) first[state] = i; else ans = max(ans, i - first[state]); } return ans; }};func findTheLongestSubstring(s string) int { bit := map[byte]int{'a': 0, 'e': 1, 'i': 2, 'o': 3, 'u': 4} first := make([]int, 32) for i := range first { first[i] = -2 } first[0] = -1 state, ans := 0, 0 for i := 0; i < len(s); i++ { if b, ok := bit[s[i]]; ok { state ^= 1 << b } if first[state] == -2 { first[state] = i } else if i-first[state] > ans { ans = i - first[state] } } return ans}class Solution: def findTheLongestSubstring(self, s: str) -> int: bit = {'a': 0, 'e': 1, 'i': 2, 'o': 3, 'u': 4} first = [-2] * 32 first[0] = -1 state = 0 ans = 0 for i, c in enumerate(s): if c in bit: state ^= 1 << bit[c] if first[state] == -2: first[state] = i else: ans = max(ans, i - first[state]) return ansvar findTheLongestSubstring = function(s) { const bit = { a: 0, e: 1, i: 2, o: 3, u: 4 }; const first = new Array(32).fill(-2); first[0] = -1; let state = 0, ans = 0; for (let i = 0; i < s.length; i++) { const c = s[i]; if (c in bit) state ^= 1 << bit[c]; if (first[state] === -2) first[state] = i; else ans = Math.max(ans, i - first[state]); } return ans;};class Solution { public int findTheLongestSubstring(String s) { Map<Character, Integer> bit = new HashMap<>(); bit.put('a', 0); bit.put('e', 1); bit.put('i', 2); bit.put('o', 3); bit.put('u', 4); int[] first = new int[32]; Arrays.fill(first, -2); first[0] = -1; int state = 0, ans = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (bit.containsKey(c)) state ^= 1 << bit.get(c); if (first[state] == -2) first[state] = i; else ans = Math.max(ans, i - first[state]); } return ans; }}function findTheLongestSubstring(s: string): number { const bit: Record<string, number> = { a: 0, e: 1, i: 2, o: 3, u: 4 }; const first: number[] = new Array(32).fill(-2); first[0] = -1; let state = 0, ans = 0; for (let i = 0; i < s.length; i++) { const c = s[i]; if (c in bit) state ^= 1 << bit[c]; if (first[state] === -2) first[state] = i; else ans = Math.max(ans, i - first[state]); } return ans;}Editorial#
The trick is recognizing “all even counts in a substring” = “running parity at both endpoints matches”. With only 5 vowels, there are exactly 32 possible parity states; storing the first index of each turns the problem into a single linear scan.
Complexity#
- Time:
O(n). - Space:
O(32).
Concept revision#
Related#