Problem
Given the string s
, return the size of the longest substring containing each vowel an even number of times. That is, ‘a’, ’e’, ‘i’, ‘o’, and ‘u’ must appear an even number of times.
Examples
Example 1:
|
|
Example 2:
|
|
Example 3:
|
|
Solution
Method 1 - Use Hashmap and Bitmask
To solve this problem, we can use a bit-manipulation approach combined with a hash map to efficiently find the longest substring where each vowel appears an even number of times.
Here is the approach:
- Bitmask Representation
- Use a bitmask to keep track of the parity (odd/even count) of each vowel.
- Each bit in the bitmask will represent whether the count of a particular vowel is odd (1) or even (0).
- For instance, the bitmask for vowels
a, e, i, o, u
could be represented with 5 bits, where each bit position could be toggled to indicate the occurrence of the respective vowel.
- HashMap to Track First Occurrences
- Use a hashMap to store the first occurrence of each bitmask state.
- This helps in calculating the length of substrings efficiently whenever we revisit the same bitmask state.
- Iterate Through the String
- While iterating through the string, update the bitmask based on the vowels encountered.
- Check the current state of the bitmask in the hashMap to determine the length of the longest valid substring ending at the current index.
So here is how it works:
- 1 State has 5 bits, each bits to indicate if the corresponding vowel have even count or not.
- When current index have a vowel character, we use bitwise xor to toggle the bit value. bitmask ^= (1 « digit);
- For the substring between two index have identical state, then all vowels’s count are even number.
- For eg. if from
[0 - i]
, we have even number of ‘a’, and from[0 - j]
, if we have even number of ‘a’ again, then the substring between i and j will have even number of ‘a’ as well. This would be the same if[0 - i]
and[0 - j]
both have odd number of ‘a’.
- For eg. if from
Video explanation
Here is the video explaining this method in detail. Please check it out:
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n)
, wheren
is the length of the string. We traverse the string once, and each operation within the loop isO(1)
. - 🧺 Space complexity:
O(1)
,O(2^5)
since the bitmask can have 32 different states (since there are 5 vowels, each can be either odd or even).
Dry Run
Initialization
stateToIndex
:{0: -1}
(We initialize with a bitmask of 0 mapped to -1 to handle cases where the entire substring from the beginning is valid.)maxLen
:0
(Tracks the length of the longest valid substring found so far.)bitmask
:0
(Tracks the current state of the vowel parities.)
Vowel Bit Position Mapping:
'a'
-> Bit 0'e'
-> Bit 1'i'
-> Bit 2'o'
-> Bit 3'u'
-> Bit 4
Iteration Through the string
- Index 0:
'e'
- Bitmask before toggle:
00000
- Toggle bit for ’e’ (bit 1):
bitmask ^= (1 << 1) -> 00010
⇨bitmask
:2
- Since
2
is not instateToIndex
, add{2: 0}
. stateToIndex
:{0: -1, 2: 0}
- Bitmask before toggle:
- Index 1:
'l'
- Bitmask remains
00010
(no change) ⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(0, 1 - 0) = 1
.
- Bitmask remains
- Index 2:
'e'
- Bitmask before toggle:
00010
- Toggle bit for ’e’ (bit 1):
bitmask ^= (1 << 1) -> 00000
⇨bitmask
:0
- Found
bitmask
0
instateToIndex
: maxLen
is updated tomax(1, 2 - (-1)) = 3
.
- Bitmask before toggle:
- Index 3:
'e'
- Bitmask before toggle:
00000
- Toggle bit for ’e’ (bit 1):
bitmask ^= (1 << 1) -> 00010
⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(3, 3 - 0) = 3
.
- Bitmask before toggle:
- Index 4:
't'
- Bitmask remains
00010
(no change) ⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(3, 4 - 0) = 4
.
- Bitmask remains
- Index 5:
'm'
- Bitmask remains
00010
(no change) ⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(4, 5 - 0) = 5
.
- Bitmask remains
- Index 6:
'i'
- Bitmask before toggle:
00010
- Toggle bit for ‘i’ (bit 2):
bitmask ^= (1 << 2) -> 00110
⇨bitmask
:6
- Since
6
is not instateToIndex
, add{6: 6}
. stateToIndex
:{0: -1, 2: 0, 6: 6}
- Bitmask before toggle:
- Index 7:
'n'
- Bitmask remains
00110
(no change) ⇨bitmask
:6
- Found
bitmask
6
instateToIndex
maxLen
remains5
.
- Bitmask remains
- Index 8:
'i'
- Bitmask before toggle:
00110
- Toggle bit for ‘i’ (bit 2):
bitmask ^= (1 << 2) -> 00010
⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(5, 8 - 0) = 8
.
- Bitmask before toggle:
- Index 9:
'c'
- Bitmask remains
00010
(no change) ⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(8, 9 - 0) = 9
.
- Bitmask remains
- Index 10:
'o'
- Bitmask before toggle:
00010
- Toggle bit for ‘o’ (bit 3):
bitmask ^= (1 << 3) -> 01010
⇨bitmask
:10
- Since
10
is not instateToIndex
, add{10: 10}
. stateToIndex
:{0: -1, 2: 0, 6: 6, 10: 10}
- Bitmask before toggle:
- Index 11:
'w'
- Bitmask remains
01010
(no change) ⇨bitmask
:10
- Found
bitmask
10
instateToIndex
maxLen
remains9
.
- Bitmask remains
- Index 12:
'o'
- Bitmask before toggle:
01010
- Toggle bit for ‘o’ (bit 3):
bitmask ^= (1 << 3) -> 00010
⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(9, 12 - 0) = 12
.
- Bitmask before toggle:
- Index 13:
'r'
- Bitmask remains
00010
(no change) ⇨bitmask
:2
- Found
bitmask
2
instateToIndex
: maxLen
is updated tomax(12, 13 - 0) = 13
.
- Bitmask remains
- Index 14:
'o'
- Bitmask before toggle:
00010
- Toggle bit for ‘o’ (bit 3):
bitmask ^= (1 << 3) -> 01010
⇨bitmask
:10
- Found
bitmask
10
instateToIndex
maxLen
remains13
.
- Bitmask before toggle:
- Index 15:
'e'
- Bitmask before toggle:
01010
- Toggle bit for ’e’ (bit 1):
bitmask ^= (1 << 1) -> 01000
⇨bitmask
:8
- Since
8
is not instateToIndex
, add{8: 15}
. stateToIndex
:{0: -1, 2: 0, 6: 6, 8: 15, 10: 10}
- Bitmask before toggle:
- Index 16:
'p'
- Bitmask remains
01000
(no change) ⇨bitmask
:8
- Found
bitmask
8
instateToIndex
maxLen
remains13
.
- Bitmask remains