Taro Logo

Find the Longest Substring Containing Vowels in Even Counts

Medium
Amazon logo
Amazon
Topics:
StringsBit ManipulationArrays

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.

For example:

Input: s = "eleetminicoworoep" Output: 13 Explanation: The longest substring is "leetminicowor" which contains two each of the vowels: e, i and o and zero of the vowels: a and u.

Input: s = "leetcodeisgreat" Output: 5 Explanation: The longest substring is "leetc" which contains two e's.

Input: s = "bcbcbc" Output: 6 Explanation: In this case, the given string "bcbcbc" is the longest because all vowels: a, e, i, o and u appear zero times.

Constraints:

1 <= s.length <= 5 x 10^5 s contains only lowercase English letters.

Solution


Naive Approach

A brute-force approach would involve checking every possible substring of the given string s and verifying if it contains each vowel ('a', 'e', 'i', 'o', 'u') an even number of times. The substring with the maximum length that satisfies this condition would be the answer.

Algorithm:

  1. Iterate through all possible start indices of the substring.
  2. For each start index, iterate through all possible end indices to define the substring.
  3. For each substring, count the occurrences of each vowel.
  4. Check if the count of each vowel is even.
  5. If all vowels appear an even number of times, update the maximum length if the current substring is longer.

Code:

def longest_substring_naive(s):
    max_len = 0
    for i in range(len(s)):
        for j in range(i, len(s)):
            sub = s[i:j+1]
            counts = {
                'a': 0,
                'e': 0,
                'i': 0,
                'o': 0,
                'u': 0
            }
            for char in sub:
                if char in counts:
                    counts[char] += 1
            even = True
            for vowel in counts:
                if counts[vowel] % 2 != 0:
                    even = False
                    break
            if even:
                max_len = max(max_len, len(sub))
    return max_len

Time Complexity:

O(n^3), where n is the length of the string s. This is because there are O(n^2) possible substrings, and for each substring, we iterate through it to count the vowels, which takes O(n) time.

Space Complexity:

O(1), as we only use a constant amount of extra space to store the vowel counts.

Optimal Approach: Using Bit Manipulation and Prefix States

A more efficient approach involves using bit manipulation to represent the state of vowel counts and a prefix state map to store the first occurrence of each state. This allows us to find the longest substring with even vowel counts in O(n) time.

Algorithm:

  1. Initialize a state variable to 0. This variable will represent the parity of each vowel count.
  2. Initialize a dictionary (or map) to store the first occurrence of each state. The key is the state, and the value is the index.
  3. Initialize the dictionary with state 0 at index -1. This means that before the string begins, all vowels have even counts.
  4. Iterate through the string:
    • Update the state based on the current character. For example, if the current character is 'a', flip the first bit of the state.
    • Check if the current state is already in the dictionary.
      • If not, store the current index as the first occurrence of this state.
      • If yes, calculate the length of the substring between the current index and the first occurrence of this state, and update the maximum length if necessary.
  5. Return the maximum length.

Code:

def longest_substring_optimal(s):
    state = 0
    state_map = {0: -1}
    max_len = 0
    for i in range(len(s)):
        if s[i] == 'a':
            state ^= 1 << 0
        elif s[i] == 'e':
            state ^= 1 << 1
        elif s[i] == 'i':
            state ^= 1 << 2
        elif s[i] == 'o':
            state ^= 1 << 3
        elif s[i] == 'u':
            state ^= 1 << 4

        if state in state_map:
            max_len = max(max_len, i - state_map[state])
        else:
            state_map[state] = i
    return max_len

Time Complexity:

O(n), where n is the length of the string s. This is because we iterate through the string only once.

Space Complexity:

O(1), since the state_map can have at most 2^5 = 32 entries, which is constant.

Edge Cases:

  • Empty string: Should return 0.
  • String with no vowels: Should return the length of the string.
  • String with all vowels appearing an even number of times: Should return the length of the string.
  • String with all vowels appearing an odd number of times: should return 0 if no substring exists that meets the condition.