LeetCode 2586 - Count the Number of Vowel Strings in Range
The problem gives us an array of lowercase strings called words and two integer indices, left and right. We need to count how many strings within the inclusive range [left, right] are considered vowel strings.
Difficulty: 🟢 Easy
Topics: Array, String, Counting
Solution
Problem Understanding
The problem gives us an array of lowercase strings called words and two integer indices, left and right. We need to count how many strings within the inclusive range [left, right] are considered vowel strings.
A string qualifies as a vowel string if both of these conditions are true:
- The first character is a vowel.
- The last character is also a vowel.
The valid vowels are:
a, e, i, o, u
The range [left, right] tells us which part of the array we should examine. We do not check every string in the array unless the range covers the entire array.
For example, if:
words = ["are","amy","u"]
left = 0
right = 2
we inspect:
"are", "amy", "u"
The word "are" starts with 'a' and ends with 'e', so it counts. The word "amy" starts with 'a' but ends with 'y', so it does not count. The word "u" starts and ends with 'u', so it counts.
The constraints are small:
words.length <= 1000words[i].length <= 10
This tells us that even a straightforward linear scan is efficient enough. We do not need advanced optimization techniques such as prefix sums or preprocessing for this specific problem size.
There are several important edge cases to consider:
- A word may contain only one character, such as
"u". In this case, the same character is both the first and last character. - The range may contain exactly one element, where
left == right. - Some words may start with vowels but end with consonants, or vice versa.
- The input guarantees all words contain only lowercase English letters, so we do not need to handle uppercase characters.
Approaches
Brute Force Approach
The brute force solution checks every string between indices left and right. For each word, we inspect the first and last characters and determine whether both belong to the vowel set.
This approach is correct because every candidate string in the required range is examined exactly once. If a word satisfies both conditions, we increment the answer counter.
Although this solution is called brute force, it is already efficient enough for the given constraints because the maximum number of words is only 1000.
Key Insight for the Optimal Approach
The key observation is that each word can be validated independently using only its first and last characters.
We do not need to analyze the entire string. Instead:
- Check
word[0] - Check
word[-1]
If both are vowels, count the word.
Using a hash set for vowels allows constant time membership checking.
Since each word is processed once and each operation is constant time, the overall solution runs in linear time relative to the number of words in the range.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(1) | Scan every word in the range and check first and last characters |
| Optimal | O(n) | O(1) | Same linear scan, optimized using constant time vowel lookup |
Here, n represents the number of words in the range [left, right].
Algorithm Walkthrough
- Create a set containing all vowels.
We use a set because membership checks are extremely fast, typically O(1). The set will contain:
{'a', 'e', 'i', 'o', 'u'}
- Initialize a counter variable to zero.
This variable stores the number of valid vowel strings found so far.
3. Iterate through the array from index left to index right.
We only inspect the required portion of the array because the problem explicitly restricts the counting range. 4. For each word, extract the first and last characters.
The first character is:
word[0]
The last character is:
word[-1]
- Check whether both characters are vowels.
If both belong to the vowel set, increment the counter. 6. After processing all words in the range, return the counter.
Why it works
The algorithm works because every word in the required range is checked exactly once against the exact definition of a vowel string. A word is counted if and only if its first and last characters are vowels. Since no valid word is skipped and no invalid word is counted, the final counter is guaranteed to be correct.
Python Solution
from typing import List
class Solution:
def vowelStrings(self, words: List[str], left: int, right: int) -> int:
vowels = {'a', 'e', 'i', 'o', 'u'}
count = 0
for index in range(left, right + 1):
word = words[index]
if word[0] in vowels and word[-1] in vowels:
count += 1
return count
The implementation begins by creating a set of vowels for fast lookup. The variable count tracks how many valid vowel strings have been found.
The loop iterates only through the indices specified by the range [left, right]. For each word, the code checks whether the first and last characters exist in the vowel set.
If both conditions are true, the counter increases by one.
Finally, the function returns the total count.
Go Solution
func vowelStrings(words []string, left int, right int) int {
vowels := map[byte]bool{
'a': true,
'e': true,
'i': true,
'o': true,
'u': true,
}
count := 0
for i := left; i <= right; i++ {
word := words[i]
if vowels[word[0]] && vowels[word[len(word)-1]] {
count++
}
}
return count
}
The Go implementation follows the same logic as the Python version. A map is used for constant time vowel lookup.
Since strings in Go are byte slices for ASCII characters, we can directly access characters using indexing like:
word[0]
and
word[len(word)-1]
The problem guarantees lowercase English letters only, so byte indexing is completely safe here.
Worked Examples
Example 1
Input:
words = ["are","amy","u"]
left = 0
right = 2
Initial state:
count = 0
vowels = {a, e, i, o, u}
| Index | Word | First Char | Last Char | Valid? | Count |
|---|---|---|---|---|---|
| 0 | "are" | a | e | Yes | 1 |
| 1 | "amy" | a | y | No | 1 |
| 2 | "u" | u | u | Yes | 2 |
Final answer:
2
Example 2
Input:
words = ["hey","aeo","mu","ooo","artro"]
left = 1
right = 4
Initial state:
count = 0
| Index | Word | First Char | Last Char | Valid? | Count |
|---|---|---|---|---|---|
| 1 | "aeo" | a | o | Yes | 1 |
| 2 | "mu" | m | u | No | 1 |
| 3 | "ooo" | o | o | Yes | 2 |
| 4 | "artro" | a | o | Yes | 3 |
Final answer:
3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each word in the range is checked once |
| Space | O(1) | The vowel set uses constant extra space |
The algorithm performs a single pass through the selected range of words. Each iteration performs only constant time operations, specifically checking two characters against a fixed-size set.
The space usage remains constant because the vowel set always contains exactly five characters regardless of input size.
Test Cases
from typing import List
class Solution:
def vowelStrings(self, words: List[str], left: int, right: int) -> int:
vowels = {'a', 'e', 'i', 'o', 'u'}
count = 0
for index in range(left, right + 1):
word = words[index]
if word[0] in vowels and word[-1] in vowels:
count += 1
return count
solution = Solution()
assert solution.vowelStrings(["are","amy","u"], 0, 2) == 2
# Basic example with mixed valid and invalid words
assert solution.vowelStrings(["hey","aeo","mu","ooo","artro"], 1, 4) == 3
# Example with partial array range
assert solution.vowelStrings(["a"], 0, 0) == 1
# Single character vowel word
assert solution.vowelStrings(["b"], 0, 0) == 0
# Single character consonant word
assert solution.vowelStrings(["abc", "ece", "ioi"], 0, 2) == 2
# Multiple valid vowel strings
assert solution.vowelStrings(["aa", "ee", "ii", "oo", "uu"], 0, 4) == 5
# Every word is valid
assert solution.vowelStrings(["ab", "cd", "ef"], 0, 2) == 0
# No valid vowel strings
assert solution.vowelStrings(["aba", "ece", "iki"], 1, 1) == 1
# Range containing exactly one valid word
assert solution.vowelStrings(["apple", "orange", "ice"], 0, 1) == 2
# Only part of the array is considered
assert solution.vowelStrings(["u", "e", "i", "o", "a"], 2, 4) == 3
# Range at the end of the array
Test Summary
| Test | Why |
|---|---|
["are","amy","u"] |
Validates the basic example |
["hey","aeo","mu","ooo","artro"] |
Validates subrange processing |
["a"] |
Tests single character vowel word |
["b"] |
Tests single character consonant word |
["abc", "ece", "ioi"] |
Tests mixed valid and invalid words |
["aa", "ee", "ii", "oo", "uu"] |
Tests all valid words |
["ab", "cd", "ef"] |
Tests zero valid words |
["aba", "ece", "iki"] with range [1,1] |
Tests single index range |
["apple", "orange", "ice"] |
Tests partial array traversal |
["u", "e", "i", "o", "a"] |
Tests range ending at final index |
Edge Cases
One important edge case is a single character word such as "u". Since the word has length one, the first and last characters are the same. A careless implementation might incorrectly assume the word must contain at least two characters. This solution correctly handles the case because both word[0] and word[-1] refer to the same character.
Another important edge case occurs when left == right. In this scenario, the range contains exactly one word. Some implementations accidentally skip processing because they use incorrect loop bounds. This solution uses:
range(left, right + 1)
which correctly includes both endpoints.
A third edge case is when no words qualify as vowel strings. For example:
["ab", "cd", "ef"]
None of these words both start and end with vowels. The implementation handles this naturally because the counter only increases when both conditions are satisfied. If no valid words are found, the initial value 0 is returned unchanged.