Problem Walkthrough

Count Vowel Strings Ranges LeetCode 2559

Solve LeetCode 2559 Count Vowel Strings in Ranges by precomputing a prefix sum of vowel-start-and-end booleans, enabling O(1) per-query range answers after a single O(n) setup pass — the Range Sum Query pattern applied to a boolean word property.

7 min read|

Count Vowel Strings

LeetCode 2559

Problem Overview

LeetCode 2559 Count Vowel Strings in Ranges gives you an array of strings words and a 2D array queries where each query is [l, r]. For each query, you must count how many strings in the inclusive range words[l] through words[r] both start with a vowel AND end with a vowel. A vowel is one of the five characters: a, e, i, o, u.

A string qualifies if its first character is a vowel AND its last character is a vowel. For a single-character string like "a", both conditions are satisfied simultaneously because the first and last characters are the same character. The query range [l, r] is inclusive on both ends, so words[l] and words[r] are both included in the count.

The number of strings can be up to 100,000 and the number of queries up to 100,000 as well. A brute-force approach — scanning every word in [l, r] for each query — would be O(q*n) in the worst case, which is up to 10 billion operations. The efficient solution uses prefix sums to answer every query in O(1) after an O(n) preprocessing step.

  • Given: array words[] and array queries[][] where each query is [l, r]
  • For each query: count words in range [l, r] that start AND end with a vowel (a, e, i, o, u)
  • A single-character word like "a" counts because first == last == vowel
  • Constraints: up to 100,000 words and up to 100,000 queries
  • Goal: return an integer array answers[] where answers[i] = count for queries[i]

Prefix Sum for Range Queries

The key insight is to convert the word array into a boolean array isVowelString[] where isVowelString[i] = 1 if words[i] starts and ends with a vowel, and 0 otherwise. Once you have this binary array, counting how many qualifying strings fall in a range [l, r] becomes a range sum problem — exactly the Range Sum Query pattern (LeetCode 303).

Build a prefix sum array prefix[] of length n+1 where prefix[0] = 0 and prefix[i] = prefix[i-1] + isVowelString[i-1]. This means prefix[i] stores the total count of vowel-bounded strings in words[0] through words[i-1]. To answer query [l, r], the answer is simply prefix[r+1] - prefix[l].

The prefix array is built once in O(n) time. Each query is then answered in O(1) by subtracting two prefix values. Total time complexity is O(n + q) where n is the number of words and q is the number of queries. Space complexity is O(n) for the prefix array.

💡

Range Sum Query Pattern (LeetCode 303)

This is the Range Sum Query pattern (LeetCode 303) applied to a boolean property. Instead of summing integers, you are summing 0s and 1s — the count of elements satisfying a condition. Precompute once, then answer unlimited queries in O(1) each. Any time you see "count or sum elements in a subarray range across multiple queries," reach for prefix sums.

Building the Prefix Array

To build the prefix array, first define a vowel set — the characters {a, e, i, o, u}. Then iterate over each word and determine whether its first character and last character are both vowels. A word qualifies if words[i][0] in vowels AND words[i][-1] in vowels (using Python slice notation). Store the boolean as 1 or 0 in isVowelString[i].

Then build the prefix array of size n+1. prefix[0] = 0 always. For each index i from 0 to n-1, set prefix[i+1] = prefix[i] + isVowelString[i]. This builds the cumulative count of vowel-bounded words from left to right. The extra element at position 0 (set to 0) is what makes the range query formula work cleanly without off-by-one errors.

An alternative implementation combines both steps: iterate over words once, and directly compute prefix as you go — prefix[i+1] = prefix[i] + (1 if words[i][0] in vowels and words[i][-1] in vowels else 0). This saves a separate boolean array allocation. Both approaches produce the same result with O(n) time and O(n) space.

  1. 1Define vowels = set("aeiou")
  2. 2Initialize prefix array of size n+1 with prefix[0] = 0
  3. 3For each i from 0 to n-1:
  4. 4 isVowel = words[i][0] in vowels AND words[i][-1] in vowels
  5. 5 prefix[i+1] = prefix[i] + (1 if isVowel else 0)
  6. 6Return prefix array for use in query answering

Answering Queries

With the prefix array built, answering each query [l, r] requires just one subtraction: answer = prefix[r+1] - prefix[l]. The value prefix[r+1] represents the total count of vowel-bounded strings in words[0..r] (inclusive). The value prefix[l] represents the total count in words[0..l-1] (exclusive of l). Their difference gives the count in words[l..r] exactly.

Iterate over all queries and compute the answer for each using this formula. Build an answers array of the same length as queries, and for each index j, set answers[j] = prefix[queries[j][1] + 1] - prefix[queries[j][0]]. Return the answers array.

The formula works even for single-element ranges: query [i, i] returns prefix[i+1] - prefix[i], which equals isVowelString[i] — exactly 1 if words[i] is vowel-bounded, 0 otherwise. Edge cases for l=0 are also handled correctly because prefix[0] = 0, so the formula returns prefix[r+1] - 0 = prefix[r+1], the full count from the start.

ℹ️

O(q*n) vs O(n + q) — Why Prefix Sums Matter

Without prefix sums, each query scans O(r-l) strings — up to O(n) per query. With q queries that is O(q*n) total, up to 10 billion operations for maximum constraints. With prefix sums, preprocessing is O(n) once and each query is O(1), giving O(n + q) total — up to 200,000 operations. The prefix sum approach is about 50,000x faster at maximum constraints.

Walk-Through Example

Example: words = ["aba", "bcb", "ece", "aa", "e"], queries = [[0,2], [1,4], [1,1]]. First build isVowelString: "aba" starts with "a" (vowel), ends with "a" (vowel) → 1. "bcb" starts with "b" (not vowel) → 0. "ece" starts with "e" (vowel), ends with "e" (vowel) → 1. "aa" starts with "a" (vowel), ends with "a" (vowel) → 1. "e" starts with "e" (vowel), ends with "e" (vowel) → 1.

Build prefix array: prefix[0]=0, prefix[1]=0+1=1, prefix[2]=1+0=1, prefix[3]=1+1=2, prefix[4]=2+1=3, prefix[5]=3+1=4. So prefix = [0, 1, 1, 2, 3, 4].

Answer queries: query [0,2] → prefix[3] - prefix[0] = 2 - 0 = 2 (strings "aba" and "ece"). Query [1,4] → prefix[5] - prefix[1] = 4 - 1 = 3 (strings "ece", "aa", "e"). Query [1,1] → prefix[2] - prefix[1] = 1 - 1 = 0 (string "bcb" is not vowel-bounded). Answer = [2, 3, 0].

  1. 1"aba" → starts a (vowel), ends a (vowel) → isVowelString=1
  2. 2"bcb" → starts b (not vowel) → isVowelString=0
  3. 3"ece" → starts e (vowel), ends e (vowel) → isVowelString=1
  4. 4"aa" → starts a (vowel), ends a (vowel) → isVowelString=1
  5. 5"e" → starts e (vowel), ends e (vowel) → isVowelString=1
  6. 6prefix = [0, 1, 1, 2, 3, 4]
  7. 7query [0,2]: prefix[3]-prefix[0] = 2-0 = 2
  8. 8query [1,4]: prefix[5]-prefix[1] = 4-1 = 3
  9. 9query [1,1]: prefix[2]-prefix[1] = 1-1 = 0

Code Walkthrough — Python and Java

Python solution: def vowelStrings(words, queries): vowels = set("aeiou"); prefix = [0] * (len(words) + 1); for i, w in enumerate(words): prefix[i+1] = prefix[i] + (1 if w[0] in vowels and w[-1] in vowels else 0); return [prefix[r+1] - prefix[l] for l, r in queries]. The list comprehension at the end answers all queries in one readable line. Using w[-1] accesses the last character in Python, which works correctly for any string length including single characters.

Java solution: public int[] vowelStrings(String[] words, int[][] queries) { Set<Character> vowels = Set.of('a','e','i','o','u'); int n = words.length; int[] prefix = new int[n + 1]; for (int i = 0; i < n; i++) { char first = words[i].charAt(0); char last = words[i].charAt(words[i].length() - 1); prefix[i+1] = prefix[i] + (vowels.contains(first) && vowels.contains(last) ? 1 : 0); } int[] ans = new int[queries.length]; for (int j = 0; j < queries.length; j++) ans[j] = prefix[queries[j][1]+1] - prefix[queries[j][0]]; return ans; }.

Both solutions run in O(n + q) time and O(n) space (prefix array). The vowel set lookup is O(1) for a fixed 5-element set. The Python version takes advantage of negative indexing for the last character; the Java version uses words[i].length() - 1. These are the only language-specific details — the algorithm is identical.

⚠️

Check BOTH First AND Last Character

Check BOTH the first AND last character — do not check only the first. A word like "apple" starts with a vowel but ends with "e" (also a vowel, so it qualifies), but "around" starts with "a" and ends with "d" (does not qualify). A single-character string like "a" satisfies both conditions because its first and last characters are the same vowel. Using w[0] and w[-1] (or charAt(0) and charAt(length-1)) correctly handles all lengths.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now