Facebook Pixel

1456. Maximum Number of Vowels in a Substring of Given Length

Problem Description

You are given a string s and an integer k. Your task is to find the maximum number of vowel letters that can appear in any substring of s that has exactly length k.

The vowel letters in English are: 'a', 'e', 'i', 'o', and 'u'.

For example, if s = "abciiidef" and k = 3, you would examine all substrings of length 3:

  • "abc" contains 1 vowel ('a')
  • "bci" contains 1 vowel ('i')
  • "cii" contains 2 vowels ('i', 'i')
  • "iii" contains 3 vowels ('i', 'i', 'i')
  • "iid" contains 2 vowels ('i', 'i')
  • "ide" contains 2 vowels ('i', 'e')
  • "def" contains 1 vowel ('e')

The maximum number of vowels in any substring of length 3 is 3, so the answer would be 3.

The solution uses a sliding window technique where:

  1. It first counts the vowels in the initial window of size k
  2. Then slides the window one position at a time by adding the new character on the right and removing the leftmost character
  3. Updates the vowel count based on whether the added/removed characters are vowels
  4. Keeps track of the maximum vowel count seen across all windows
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that we need to check every possible substring of length k in the string s. A naive approach would be to generate all substrings of length k and count vowels in each one, but this would involve redundant counting.

Consider what happens when we move from one substring to the next consecutive substring of length k. For example, if we have the substring "abc" and move to "bcd", we're essentially:

  • Removing the first character 'a' from our window
  • Adding a new character 'd' at the end

This observation leads us to the sliding window technique. Instead of recounting all vowels in each substring from scratch, we can maintain a running count and simply adjust it based on what enters and exits our window.

The process becomes:

  1. Count vowels in the first k characters (our initial window)
  2. Slide the window one position: remove the leftmost character's contribution and add the new rightmost character's contribution
  3. If the removed character was a vowel, decrease our count by 1
  4. If the added character is a vowel, increase our count by 1
  5. Track the maximum count seen so far

This optimization reduces our time complexity from O(n * k) (checking each character in every substring) to O(n) (checking each character only twice - once when it enters the window and once when it leaves).

The elegance of this approach lies in how we transform the problem from "count vowels in multiple substrings" to "maintain a single vowel count that we adjust as we slide through the string."

Learn more about Sliding Window patterns.

Solution Approach

The implementation uses a sliding window technique to efficiently find the maximum number of vowels in any substring of length k.

Step 1: Initialize the vowel set and first window

First, we create a set containing all vowels vowels = set("aeiou") for O(1) lookup time. Then we count the vowels in the first k characters using a generator expression: sum(c in vowels for c in s[:k]). This gives us our initial count cnt and we set our answer ans to this initial value.

Step 2: Slide the window through the string

Starting from index k, we iterate through the rest of the string. For each position i:

  • We add the character at position i to our window (the rightmost character)
  • We remove the character at position i - k from our window (the leftmost character)

The count update is done efficiently in one line:

cnt += int(s[i] in vowels) - int(s[i - k] in vowels)

This works by:

  • int(s[i] in vowels) returns 1 if the new character is a vowel, 0 otherwise
  • int(s[i - k] in vowels) returns 1 if the removed character is a vowel, 0 otherwise
  • The difference gives us the net change in vowel count

Step 3: Track the maximum

After each window adjustment, we update our answer: ans = max(ans, cnt). This ensures we keep track of the maximum number of vowels seen in any window of size k.

Time and Space Complexity:

  • Time: O(n) where n is the length of string s. We visit each character at most twice (once when entering the window, once when leaving).
  • Space: O(1) as we only use a constant amount of extra space for the vowel set and counters.

The beauty of this approach is that instead of recalculating the vowel count for each window from scratch, we maintain a running count and adjust it incrementally, making the solution highly efficient.

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 walk through the solution with s = "leetcode" and k = 3.

Initial Setup:

  • Create vowel set: vowels = {'a', 'e', 'i', 'o', 'u'}
  • First window is "lee" (indices 0-2)
  • Count vowels in "lee": 'l' (not vowel), 'e' (vowel), 'e' (vowel) → cnt = 2
  • Set ans = 2

Sliding the Window:

Position i=3: Window moves from "lee" to "eet"

  • Add s[3] = 't' (not a vowel) → add 0
  • Remove s[0] = 'l' (not a vowel) → subtract 0
  • cnt = 2 + 0 - 0 = 2
  • ans = max(2, 2) = 2

Position i=4: Window moves from "eet" to "etc"

  • Add s[4] = 'c' (not a vowel) → add 0
  • Remove s[1] = 'e' (vowel) → subtract 1
  • cnt = 2 + 0 - 1 = 1
  • ans = max(2, 1) = 2

Position i=5: Window moves from "etc" to "tco"

  • Add s[5] = 'o' (vowel) → add 1
  • Remove s[2] = 'e' (vowel) → subtract 1
  • cnt = 1 + 1 - 1 = 1
  • ans = max(2, 1) = 2

Position i=6: Window moves from "tco" to "cod"

  • Add s[6] = 'd' (not a vowel) → add 0
  • Remove s[3] = 't' (not a vowel) → subtract 0
  • cnt = 1 + 0 - 0 = 1
  • ans = max(2, 1) = 2

Position i=7: Window moves from "cod" to "ode"

  • Add s[7] = 'e' (vowel) → add 1
  • Remove s[4] = 'c' (not a vowel) → subtract 0
  • cnt = 1 + 1 - 0 = 2
  • ans = max(2, 2) = 2

Result: The maximum number of vowels in any substring of length 3 is 2.

The key insight demonstrated here is how we maintain a running count (cnt) that gets adjusted by exactly the difference between what enters and exits the window, avoiding the need to recount all characters in each window.

Solution Implementation

1class Solution:
2    def maxVowels(self, s: str, k: int) -> int:
3        # Define the set of vowels for O(1) lookup
4        vowels = {'a', 'e', 'i', 'o', 'u'}
5      
6        # Initialize the count of vowels in the first window of size k
7        current_count = sum(1 for char in s[:k] if char in vowels)
8      
9        # Set the maximum count to the initial window count
10        max_count = current_count
11      
12        # Use sliding window technique to check remaining windows
13        for i in range(k, len(s)):
14            # Add 1 if the new character entering the window is a vowel
15            if s[i] in vowels:
16                current_count += 1
17          
18            # Subtract 1 if the character leaving the window is a vowel
19            if s[i - k] in vowels:
20                current_count -= 1
21          
22            # Update the maximum count if current window has more vowels
23            max_count = max(max_count, current_count)
24      
25        return max_count
26
1class Solution {
2    /**
3     * Finds the maximum number of vowels in any substring of length k.
4     * Uses sliding window technique to efficiently count vowels.
5     * 
6     * @param s the input string to search
7     * @param k the length of the substring window
8     * @return the maximum number of vowels found in any substring of length k
9     */
10    public int maxVowels(String s, int k) {
11        // Count vowels in the initial window of size k
12        int currentVowelCount = 0;
13        for (int i = 0; i < k; i++) {
14            if (isVowel(s.charAt(i))) {
15                currentVowelCount++;
16            }
17        }
18      
19        // Initialize the maximum count with the first window's count
20        int maxVowelCount = currentVowelCount;
21      
22        // Slide the window through the rest of the string
23        for (int i = k; i < s.length(); i++) {
24            // Add the new character entering the window
25            if (isVowel(s.charAt(i))) {
26                currentVowelCount++;
27            }
28          
29            // Remove the character leaving the window
30            if (isVowel(s.charAt(i - k))) {
31                currentVowelCount--;
32            }
33          
34            // Update the maximum count if current window has more vowels
35            maxVowelCount = Math.max(maxVowelCount, currentVowelCount);
36        }
37      
38        return maxVowelCount;
39    }
40
41    /**
42     * Checks if a character is a vowel (a, e, i, o, 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 maxVowels(string s, int k) {
4        // Lambda function to check if a character is a vowel
5        auto isVowel = [](char c) {
6            return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
7        };
8      
9        // Count vowels in the first window of size k
10        int currentVowelCount = count_if(s.begin(), s.begin() + k, isVowel);
11      
12        // Initialize the maximum vowel count with the first window
13        int maxVowelCount = currentVowelCount;
14      
15        // Slide the window through the string
16        for (int i = k; i < s.size(); ++i) {
17            // Add new character entering the window and remove the one leaving
18            currentVowelCount += isVowel(s[i]) - isVowel(s[i - k]);
19          
20            // Update maximum if current window has more vowels
21            maxVowelCount = max(maxVowelCount, currentVowelCount);
22        }
23      
24        return maxVowelCount;
25    }
26};
27
1/**
2 * Finds the maximum number of vowels in any substring of length k
3 * @param s - The input string to search through
4 * @param k - The length of the substring window
5 * @returns The maximum count of vowels found in any k-length substring
6 */
7function maxVowels(s: string, k: number): number {
8    // Define the set of vowels for O(1) lookup
9    const vowelSet: Set<string> = new Set<string>(['a', 'e', 'i', 'o', 'u']);
10  
11    // Initialize vowel count for the first window of size k
12    let currentVowelCount: number = 0;
13    for (let i: number = 0; i < k; i++) {
14        if (vowelSet.has(s[i])) {
15            currentVowelCount++;
16        }
17    }
18  
19    // Track the maximum vowel count found so far
20    let maxVowelCount: number = currentVowelCount;
21  
22    // Slide the window through the rest of the string
23    for (let i: number = k; i < s.length; i++) {
24        // Add the new character entering the window
25        if (vowelSet.has(s[i])) {
26            currentVowelCount++;
27        }
28      
29        // Remove the character leaving the window
30        if (vowelSet.has(s[i - k])) {
31            currentVowelCount--;
32        }
33      
34        // Update maximum if current window has more vowels
35        maxVowelCount = Math.max(maxVowelCount, currentVowelCount);
36    }
37  
38    return maxVowelCount;
39}
40

Time and Space Complexity

Time Complexity: O(n), where n is the length of the string s.

The algorithm performs the following operations:

  • Initial window calculation: O(k) to count vowels in the first k characters
  • Sliding window iteration: O(n-k) iterations, each taking O(1) time to update the count by checking if characters are vowels
  • Total time: O(k) + O(n-k) = O(n)

Space Complexity: O(1)

The algorithm uses:

  • A constant-size set vowels containing 5 characters: O(1)
  • Fixed number of integer variables (ans, cnt, i): O(1)
  • No additional data structures that scale with input size

The space usage remains constant regardless of the input string length.

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

Common Pitfalls

1. Off-by-One Error in Window Boundaries

A common mistake is incorrectly calculating which character to remove when sliding the window. Developers often confuse whether to remove the character at index i - k or i - k + 1.

Incorrect approach:

# Wrong: removes the wrong character
for i in range(k, len(s)):
    if s[i] in vowels:
        current_count += 1
    if s[i - k + 1] in vowels:  # This is wrong!
        current_count -= 1

Why it's wrong: When we're at position i, our window should include characters from index i - k + 1 to i. So we need to remove the character at position i - k (not i - k + 1).

Solution: Always remember that when adding character at index i, remove the character at index i - k to maintain window size of exactly k.

2. Not Handling Edge Cases

Failing to handle cases where k is larger than the string length or when the string is empty.

Problematic code:

def maxVowels(self, s: str, k: int) -> int:
    vowels = {'a', 'e', 'i', 'o', 'u'}
    current_count = sum(1 for char in s[:k] if char in vowels)  # Fails if k > len(s)

Solution: Add validation at the beginning:

def maxVowels(self, s: str, k: int) -> int:
    if not s or k > len(s):
        return 0
    # ... rest of the code

3. Case Sensitivity Issues

The problem specifies lowercase vowels, but not accounting for potential uppercase letters can lead to incorrect results if the input isn't guaranteed to be lowercase.

Problematic assumption:

vowels = {'a', 'e', 'i', 'o', 'u'}  # Only handles lowercase

Solution: Either convert the string to lowercase first or include both cases:

# Option 1: Include both cases
vowels = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'}

# Option 2: Convert to lowercase
s = s.lower()
vowels = {'a', 'e', 'i', 'o', 'u'}

4. Inefficient Initial Window Calculation

Using inefficient methods to count vowels in the initial window, such as creating unnecessary intermediate lists.

Inefficient approach:

# Creates an unnecessary list in memory
initial_vowels = [char for char in s[:k] if char in vowels]
current_count = len(initial_vowels)

Solution: Use a generator expression with sum() for memory efficiency:

current_count = sum(1 for char in s[:k] if char in vowels)

5. Forgetting to Update Maximum

Some implementations might forget to consider the initial window as a potential maximum, only updating during the sliding phase.

Incomplete logic:

max_count = 0  # Should be initialized with first window count
for i in range(k, len(s)):
    # sliding logic...
    max_count = max(max_count, current_count)
# Misses the case where the first window itself is the maximum

Solution: Always initialize max_count with the count from the first window:

current_count = sum(1 for char in s[:k] if char in vowels)
max_count = current_count  # Don't forget this initialization!
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