Facebook Pixel

2062. Count Vowel Substrings of a String

EasyHash TableString
Leetcode Link

Problem Description

You need to find and count all special substrings in a given string that meet two specific criteria:

  1. The substring must contain only vowels ('a', 'e', 'i', 'o', 'u')
  2. The substring must contain all five vowels at least once

A substring is a continuous sequence of characters from the original string. For example, in the string "hello", some substrings are "h", "he", "ell", "llo", etc.

The task is to count how many such "vowel substrings" exist in the input string word.

For example:

  • If word = "aeiouu", there are 2 valid vowel substrings: "aeiou" and "aeiouu" (both contain all 5 vowels and only vowels)
  • If word = "unicornarihan", there are 0 valid vowel substrings (no substring contains all 5 vowels using only vowel characters)

The solution works by:

  1. Checking every possible starting position i in the string
  2. For each starting position, extending rightward character by character
  3. Tracking which vowels have been seen using a set t
  4. If a non-vowel is encountered, stopping the current extension
  5. When all 5 vowels are present (checked by len(t) == 5), counting it as a valid vowel substring
  6. Continuing to extend rightward to find more valid substrings from the same starting position

The time complexity is O(n²) where n is the length of the string, since we examine all possible substrings that contain only vowels.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to find substrings that satisfy two constraints simultaneously: containing only vowels AND containing all five vowels. This suggests we need to track two things as we explore substrings: whether each character is a vowel, and which vowels we've seen so far.

Since we need contiguous substrings, a natural approach is to consider all possible starting positions and extend from each one. For any starting position i, we can try to extend the substring by adding characters one by one to the right.

Why does this make sense? Because:

  1. If we hit a consonant at any point, we know that no substring starting at i and extending beyond that consonant can be valid (it would contain a non-vowel)
  2. We need to keep track of which vowels we've collected so far - a set is perfect for this since it automatically handles duplicates
  3. Once we have all 5 vowels in our set, every extension that still contains only vowels gives us another valid substring

The beauty of this approach is its simplicity - we don't need complex data structures or algorithms. We just systematically check every possible vowel-only substring and count those that contain all five vowels.

The condition len(t) == 5 is elegant because it automatically tells us when we've seen all five distinct vowels (since the set only stores unique elements). Every time this condition becomes true as we extend our substring, we've found another valid vowel substring to count.

This brute force approach works well here because the constraint (containing only vowels) naturally limits how far we need to search from each starting position - we stop as soon as we hit a consonant.

Solution Approach

The solution uses a brute force enumeration approach combined with a hash table (set) to track vowels.

Step-by-step implementation:

  1. Initialize the vowel set: Create a set s containing all five vowels "aeiou" for quick vowel checking using O(1) lookup time.

  2. Set up variables: Initialize ans = 0 to count valid substrings and get the string length n.

  3. Enumerate left endpoints: Use the outer loop with index i from 0 to n-1 to represent each possible starting position of a substring.

  4. Track vowels for current substring: For each starting position i, create an empty set t to track which distinct vowels appear in the current substring being built.

  5. Extend the substring rightward: The inner loop iterates through characters starting from position i using slice notation word[i:]. For each character c:

    • Check if it's a vowel: If c not in s, the character is a consonant, so we break immediately since no valid substring can extend beyond this point
    • Add vowel to tracking set: If it's a vowel, add it to set t using t.add(c)
    • Check for valid substring: After adding the vowel, check if len(t) == 5. If true, we have all five vowels, making this a valid vowel substring, so increment ans += 1
  6. Return the count: After examining all possible substrings, return ans.

Why this works:

  • The nested loop structure ensures we check every possible substring that starts with a vowel
  • The set t automatically handles duplicate vowels - we only care about having each vowel type at least once
  • Breaking on consonants optimizes the search by avoiding unnecessary iterations
  • The condition ans += len(t) == 5 is a concise way to increment the counter (adds 1 when true, 0 when false)

Time Complexity: O(n²) where n is the length of the string, as we potentially examine all substrings. Space Complexity: O(1) since the sets can contain at most 5 elements.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with word = "aeioubbb".

Initial Setup:

  • s = {'a', 'e', 'i', 'o', 'u'} (vowel set for checking)
  • ans = 0 (counter for valid substrings)
  • n = 8 (length of "aeioubbb")

Iteration with i = 0 (starting at 'a'):

  • Initialize t = {} (empty set to track vowels)
  • Examine substring starting from index 0:
    • Character 'a': Is vowel ✓, add to t → t = {'a'}, len(t) = 1 ≠ 5
    • Character 'e': Is vowel ✓, add to t → t = {'a', 'e'}, len(t) = 2 ≠ 5
    • Character 'i': Is vowel ✓, add to t → t = {'a', 'e', 'i'}, len(t) = 3 ≠ 5
    • Character 'o': Is vowel ✓, add to t → t = {'a', 'e', 'i', 'o'}, len(t) = 4 ≠ 5
    • Character 'u': Is vowel ✓, add to t → t = {'a', 'e', 'i', 'o', 'u'}, len(t) = 5 ✓, ans = 1 (found "aeiou")
    • Character 'b': Not a vowel ✗, break inner loop

Iteration with i = 1 (starting at 'e'):

  • Initialize t = {}
  • Examine substring starting from index 1:
    • Character 'e': Is vowel ✓, add to t → t = {'e'}, len(t) = 1 ≠ 5
    • Character 'i': Is vowel ✓, add to t → t = {'e', 'i'}, len(t) = 2 ≠ 5
    • Character 'o': Is vowel ✓, add to t → t = {'e', 'i', 'o'}, len(t) = 3 ≠ 5
    • Character 'u': Is vowel ✓, add to t → t = {'e', 'i', 'o', 'u'}, len(t) = 4 ≠ 5
    • Character 'b': Not a vowel ✗, break inner loop

Iterations i = 2, 3, 4: Similar pattern - each builds a set of vowels but never reaches all 5 before hitting 'b'

Iterations i = 5, 6, 7 (starting at 'b'):

  • First character 'b' is not a vowel, immediately break inner loop

Final Result: ans = 1

The algorithm found exactly one valid vowel substring: "aeiou" (from indices 0-4).

Solution Implementation

1class Solution:
2    def countVowelSubstrings(self, word: str) -> int:
3        # Define the set of vowels for quick lookup
4        vowels = set("aeiou")
5      
6        # Initialize counter for valid substrings and get word length
7        count = 0
8        word_length = len(word)
9      
10        # Iterate through each possible starting position
11        for start_index in range(word_length):
12            # Track unique vowels found in current substring
13            found_vowels = set()
14          
15            # Extend substring from current starting position
16            for char in word[start_index:]:
17                # If we encounter a non-vowel, stop extending this substring
18                if char not in vowels:
19                    break
20              
21                # Add current vowel to our set
22                found_vowels.add(char)
23              
24                # If we have all 5 vowels, increment counter
25                if len(found_vowels) == 5:
26                    count += 1
27      
28        return count
29
1class Solution {
2    /**
3     * Counts the number of substrings that contain all five vowels (a, e, i, o, u).
4     * A valid substring must consist only of vowels and contain all five distinct vowels.
5     * 
6     * @param word the input string to search for vowel substrings
7     * @return the count of valid vowel substrings
8     */
9    public int countVowelSubstrings(String word) {
10        int length = word.length();
11        int count = 0;
12      
13        // Iterate through each possible starting position
14        for (int startIndex = 0; startIndex < length; startIndex++) {
15            // Use a set to track unique vowels found in current substring
16            Set<Character> uniqueVowels = new HashSet<>();
17          
18            // Extend the substring from current starting position
19            for (int endIndex = startIndex; endIndex < length; endIndex++) {
20                char currentChar = word.charAt(endIndex);
21              
22                // If current character is not a vowel, break the inner loop
23                // as we need continuous vowel substrings
24                if (!isVowel(currentChar)) {
25                    break;
26                }
27              
28                // Add the vowel to our set of unique vowels
29                uniqueVowels.add(currentChar);
30              
31                // If we have all 5 vowels, increment the count
32                if (uniqueVowels.size() == 5) {
33                    count++;
34                }
35            }
36        }
37      
38        return count;
39    }
40  
41    /**
42     * Checks if a character is a vowel (a, e, i, o, or u).
43     * 
44     * @param c the character to check
45     * @return true if the character is a vowel, false otherwise
46     */
47    private boolean isVowel(char c) {
48        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
49    }
50}
51
1class Solution {
2public:
3    int countVowelSubstrings(string word) {
4        int result = 0;
5        int length = word.size();
6      
7        // Iterate through each starting position
8        for (int start = 0; start < length; ++start) {
9            unordered_set<char> vowelSet;
10          
11            // Extend substring from current starting position
12            for (int end = start; end < length; ++end) {
13                char currentChar = word[end];
14              
15                // Break if we encounter a non-vowel character
16                if (!isVowel(currentChar)) {
17                    break;
18                }
19              
20                // Add current vowel to the set
21                vowelSet.insert(currentChar);
22              
23                // If we have all 5 vowels, increment the count
24                if (vowelSet.size() == 5) {
25                    result++;
26                }
27            }
28        }
29      
30        return result;
31    }
32
33private:
34    // Helper function to check if a character is a vowel
35    bool isVowel(char c) {
36        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
37    }
38};
39
1/**
2 * Counts the number of substrings that contain all five vowels (a, e, i, o, u)
3 * @param word - The input string to search for vowel substrings
4 * @returns The count of substrings containing all five vowels
5 */
6function countVowelSubstrings(word: string): number {
7    let count: number = 0;
8    const length: number = word.length;
9  
10    // Iterate through each starting position in the word
11    for (let startIndex: number = 0; startIndex < length; startIndex++) {
12        // Set to track unique vowels found in current substring
13        const vowelsFound: Set<string> = new Set<string>();
14      
15        // Extend substring from current starting position
16        for (let endIndex: number = startIndex; endIndex < length; endIndex++) {
17            const currentChar: string = word[endIndex];
18          
19            // Check if current character is a vowel
20            if (!(currentChar === 'a' || currentChar === 'e' || currentChar === 'i' || 
21                  currentChar === 'o' || currentChar === 'u')) {
22                // Break if non-vowel character is encountered
23                break;
24            }
25          
26            // Add vowel to the set
27            vowelsFound.add(currentChar);
28          
29            // Increment count if all 5 vowels are present
30            if (vowelsFound.size === 5) {
31                count++;
32            }
33        }
34    }
35  
36    return count;
37}
38

Time and Space Complexity

The time complexity is O(n^2), where n is the length of the string word. This is because we have two nested loops: the outer loop iterates through each starting position i from 0 to n-1, and for each starting position, the inner loop iterates through the substring starting at position i to potentially the end of the string. In the worst case, for each of the n starting positions, we examine up to n characters, resulting in O(n * n) = O(n^2) operations.

The space complexity is O(C), where C is the size of the character set. In this problem, we use two sets: s which stores the 5 vowels ("aeiou"), and t which accumulates the vowels found in the current substring. Since both sets can contain at most 5 distinct vowel characters, the space complexity is O(5) = O(1), which can be generalized as O(C) where C = 5 represents the number of distinct vowels.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Misunderstanding Substring Counting

The Problem: A common mistake is thinking that once we find all 5 vowels from a starting position, we should stop checking and move to the next starting position. This would miss valid substrings that extend beyond the first occurrence of all 5 vowels.

Incorrect Approach:

for start_index in range(word_length):
    found_vowels = set()
    for char in word[start_index:]:
        if char not in vowels:
            break
        found_vowels.add(char)
        if len(found_vowels) == 5:
            count += 1
            break  # WRONG: Breaking here misses longer valid substrings

Why it's wrong: Consider the string "aeiouu". From position 0:

  • "aeiou" is valid (count = 1)
  • "aeiouu" is also valid (count = 2)

Breaking after finding the first valid substring would miss "aeiouu".

Correct Solution: Continue extending the substring even after finding all 5 vowels, as shown in the original code. Each extension that still contains all 5 vowels is a distinct valid substring.

Pitfall 2: Using Index-Based Inner Loop Incorrectly

The Problem: Using indices for both loops but forgetting to adjust the inner loop's range can lead to incorrect results or inefficiency.

Incorrect Approach:

for i in range(word_length):
    found_vowels = set()
    for j in range(word_length):  # WRONG: Should start from i
        if word[j] not in vowels:
            break
        found_vowels.add(word[j])
        if len(found_vowels) == 5:
            count += 1

Why it's wrong: This examines the same prefix repeatedly instead of different substrings starting at position i.

Correct Solution: Either use range(i, word_length) for the inner loop or use slice notation word[i:] as in the original solution:

for i in range(word_length):
    found_vowels = set()
    for j in range(i, word_length):  # Correct: Start from i
        if word[j] not in vowels:
            break
        found_vowels.add(word[j])
        if len(found_vowels) == 5:
            count += 1

Pitfall 3: Resetting the Vowel Tracker Incorrectly

The Problem: Forgetting to reset the vowel tracking set for each new starting position, or resetting it at the wrong time.

Incorrect Approach:

found_vowels = set()  # WRONG: Declared outside the outer loop
for start_index in range(word_length):
    for char in word[start_index:]:
        if char not in vowels:
            found_vowels = set()  # WRONG: Reset only on consonant
            break
        found_vowels.add(char)
        if len(found_vowels) == 5:
            count += 1

Why it's wrong: The set should be fresh for each new starting position, not carried over from previous iterations or reset only when hitting a consonant.

Correct Solution: Initialize found_vowels = set() inside the outer loop but before the inner loop, ensuring each starting position gets a clean slate for tracking vowels.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More