Count Vowel Strings in Ranges

MEDIUM

Description

You are given a 0-indexed array of strings words and a 2D array of integers queries.

Each query queries[i] = [li, ri] asks us to find the number of strings present at the indices ranging from li to ri (both inclusive) of words that start and end with a vowel.

Return an array ans of size queries.length, where ans[i] is the answer to the ith query.

Note that the vowel letters are 'a', 'e', 'i', 'o', and 'u'.

 

Example 1:

Input: words = ["aba","bcb","ece","aa","e"], queries = [[0,2],[1,4],[1,1]]
Output: [2,3,0]
Explanation: The strings starting and ending with a vowel are "aba", "ece", "aa" and "e".
The answer to the query [0,2] is 2 (strings "aba" and "ece").
to query [1,4] is 3 (strings "ece", "aa", "e").
to query [1,1] is 0.
We return [2,3,0].

Example 2:

Input: words = ["a","e","i"], queries = [[0,2],[0,1],[2,2]]
Output: [3,2,1]
Explanation: Every string satisfies the conditions, so we return [3,2,1].

 

Constraints:

  • 1 <= words.length <= 105
  • 1 <= words[i].length <= 40
  • words[i] consists only of lowercase English letters.
  • sum(words[i].length) <= 3 * 105
  • 1 <= queries.length <= 105
  • 0 <= li <= ri < words.length

Approaches

Checkout 2 different approaches to solve Count Vowel Strings in Ranges. Click on different approaches to view the approach and algorithm in detail.

Prefix Sum Precomputation

A much more efficient approach is to precompute the counts to answer range queries quickly. The problem of finding the number of items with a certain property in a range is a classic application of the prefix sum technique. We can first determine for each word if it's a "vowel string" (starts and ends with a vowel). Then, we can build a prefix sum array on top of this information. The prefix sum array, prefixCounts, will store at index i the cumulative count of vowel strings from the beginning of the words array up to index i-1. With this prefixCounts array, the answer to any query [l, r] can be found in constant time by calculating prefixCounts[r+1] - prefixCounts[l].

Algorithm

  1. Create a prefix sum array, prefixCounts, of size N + 1, where N is the length of the words array. Initialize prefixCounts[0] = 0.
  2. Create a helper function isVowel(char c).
  3. Iterate through the words array from i = 0 to N-1.
  4. For each word, check if it starts and ends with a vowel.
  5. Calculate the value for the current position in the prefix sum array: prefixCounts[i+1] = prefixCounts[i] + (1 if the word is a vowel string, else 0).
  6. After this single pass, the prefixCounts array is fully populated. prefixCounts[k] now holds the count of vowel strings in words[0...k-1].
  7. Initialize an answer array ans of size Q, where Q is the number of queries.
  8. Iterate through each query [l, r].
  9. The number of vowel strings in the range [l, r] is the total count up to index r minus the total count up to index l-1. This can be calculated in O(1) time as prefixCounts[r + 1] - prefixCounts[l].
  10. Store this result in the ans array.
  11. Return ans.

This approach involves two main phases: precomputation and query processing.

In the precomputation phase, we iterate through the words array just once. We maintain a running count of vowel strings and store it in a prefixCounts array. prefixCounts[i+1] will store the total number of vowel strings found in the subarray words[0...i].

In the query processing phase, we can answer any range query [l, r] instantly. The count of vowel strings from index l to r is simply the total count up to r minus the total count up to l-1. Using our prefixCounts array, this translates to prefixCounts[r+1] - prefixCounts[l]. This decouples the query processing time from the size of the range, making it extremely fast.

class Solution {
    private boolean isVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }

    public int[] vowelStrings(String[] words, int[][] queries) {
        int n = words.length;
        int[] prefixCounts = new int[n + 1];
        
        // Build the prefix sum array
        for (int i = 0; i < n; i++) {
            String word = words[i];
            int vowelFlag = 0;
            if (isVowel(word.charAt(0)) && isVowel(word.charAt(word.length() - 1))) {
                vowelFlag = 1;
            }
            prefixCounts[i + 1] = prefixCounts[i] + vowelFlag;
        }
        
        int[] ans = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            int l = queries[i][0];
            int r = queries[i][1];
            // The count for range [l, r] is count(0..r) - count(0..l-1)
            // which corresponds to prefixCounts[r+1] - prefixCounts[l]
            ans[i] = prefixCounts[r + 1] - prefixCounts[l];
        }
        
        return ans;
    }
}

Complexity Analysis

Time Complexity: O(N + Q), where N is the number of words and Q is the number of queries. We perform one pass over the `words` array to build the prefix sum array (O(N)), and one pass over the `queries` array to compute the answers (O(Q)). Each query is answered in O(1) time. This is very efficient and well within the time limits.Space Complexity: O(N), where N is the length of `words`. This is the auxiliary space required to store the prefix sum array.

Pros and Cons

Pros:
  • Highly efficient, with linear time complexity.
  • Answers each range query in constant time after the initial precomputation.
Cons:
  • Requires extra space proportional to the number of words to store the prefix sum array.

Code Solutions

Checking out 3 solutions in different languages for Count Vowel Strings in Ranges. Click on different languages to view the code.


Video Solution

Watch the video walkthrough for Count Vowel Strings in Ranges



Patterns:

Prefix Sum

Data Structures:

ArrayString

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.