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:
- It first counts the vowels in the initial window of size
k
- Then slides the window one position at a time by adding the new character on the right and removing the leftmost character
- Updates the vowel count based on whether the added/removed characters are vowels
- Keeps track of the maximum vowel count seen across all windows
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:
- Count vowels in the first
k
characters (our initial window) - Slide the window one position: remove the leftmost character's contribution and add the new rightmost character's contribution
- If the removed character was a vowel, decrease our count by 1
- If the added character is a vowel, increase our count by 1
- 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 otherwiseint(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 EvaluatorExample 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 firstk
characters - Sliding window iteration:
O(n-k)
iterations, each takingO(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!
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
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!