2062. Count Vowel Substrings of a String
Problem Description
You need to find and count all special substrings in a given string that meet two specific criteria:
- The substring must contain only vowels ('a', 'e', 'i', 'o', 'u')
- 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:
- Checking every possible starting position
i
in the string - For each starting position, extending rightward character by character
- Tracking which vowels have been seen using a set
t
- If a non-vowel is encountered, stopping the current extension
- When all 5 vowels are present (checked by
len(t) == 5
), counting it as a valid vowel substring - 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.
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:
- 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) - We need to keep track of which vowels we've collected so far - a set is perfect for this since it automatically handles duplicates
- 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:
-
Initialize the vowel set: Create a set
s
containing all five vowels"aeiou"
for quick vowel checking usingO(1)
lookup time. -
Set up variables: Initialize
ans = 0
to count valid substrings and get the string lengthn
. -
Enumerate left endpoints: Use the outer loop with index
i
from0
ton-1
to represent each possible starting position of a substring. -
Track vowels for current substring: For each starting position
i
, create an empty sett
to track which distinct vowels appear in the current substring being built. -
Extend the substring rightward: The inner loop iterates through characters starting from position
i
using slice notationword[i:]
. For each characterc
:- 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
usingt.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 incrementans += 1
- Check if it's a vowel: If
-
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 EvaluatorExample 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
- Character 'a': Is vowel ✓, add to t →
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
- Character 'e': Is vowel ✓, add to t →
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.
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!