Before attempting this problem, you should be comfortable with:
Sliding Window Technique - The optimal solution maintains a fixed-size window that slides across the string
Hash Set / Set Operations - Used for O(1) vowel membership checking
Prefix Sum - An alternative approach uses cumulative sums to answer range queries efficiently
Bit Manipulation (optional) - Advanced optimization uses bitmasks for faster vowel checking
1. Brute Force
Intuition
The most straightforward approach is to check every possible substring of length k. For each starting position, count the vowels in the window and track the maximum count seen.
This works correctly but is inefficient because we recount characters for overlapping windows.
classSolution:defmaxVowels(self, s:str, k:int)->int:
vowel ={'a','e','i','o','u'}
res =0for i inrange(len(s)- k +1):
cnt =0for j inrange(i, i + k):
cnt +=1if s[j]in vowel else0
res =max(res, cnt)return res
publicclassSolution{publicintmaxVowels(String s,int k){Set<Character> vowels =Set.of('a','e','i','o','u');int res =0;for(int i =0; i <= s.length()- k; i++){int cnt =0;for(int j = i; j < i + k; j++){if(vowels.contains(s.charAt(j))){
cnt++;}}
res =Math.max(res, cnt);}return res;}}
classSolution{public:intmaxVowels(string s,int k){
unordered_set<char> vowels ={'a','e','i','o','u'};int res =0;for(int i =0; i <= s.size()- k; i++){int cnt =0;for(int j = i; j < i + k; j++){if(vowels.count(s[j])){
cnt++;}}
res =max(res, cnt);}return res;}};
classSolution{/**
* @param {string} s
* @param {number} k
* @return {number}
*/maxVowels(s, k){const vowels =newSet(['a','e','i','o','u']);let res =0;for(let i =0; i <= s.length - k; i++){let cnt =0;for(let j = i; j < i + k; j++){if(vowels.has(s[j])){
cnt++;}}
res = Math.max(res, cnt);}return res;}}
funcmaxVowels(s string, k int)int{
vowels :=map[byte]bool{'a':true,'e':true,'i':true,'o':true,'u':true}
res :=0for i :=0; i <=len(s)-k; i++{
cnt :=0for j := i; j < i+k; j++{if vowels[s[j]]{
cnt++}}if cnt > res {
res = cnt
}}return res
}
class Solution {funmaxVowels(s: String, k: Int): Int {val vowels =setOf('a','e','i','o','u')var res =0for(i in0..s.length - k){var cnt =0for(j in i until i + k){if(s[j]in vowels){
cnt++}}
res =maxOf(res, cnt)}return res
}}
classSolution{funcmaxVowels(_ s:String,_ k:Int)->Int{let vowels:Set<Character>=["a","e","i","o","u"]let chars =Array(s)var res =0for i in0...(chars.count - k){var cnt =0for j in i..<(i + k){if vowels.contains(chars[j]){
cnt +=1}}
res =max(res, cnt)}return res
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(1) extra space.
2. Prefix Count
Intuition
We can precompute a prefix sum array where prefix[i] stores the number of vowels in s[0..i-1]. Then, the vowel count in any window s[i-k..i-1] is simply prefix[i] - prefix[i-k].
This allows us to answer each window query in O(1) time after O(n) preprocessing.
Algorithm
Build a prefix array where prefix[i+1] = prefix[i] + (1 if s[i] is a vowel else 0).
For each ending position i from k to n:
Calculate the vowel count as prefix[i] - prefix[i-k].
classSolution:defmaxVowels(self, s:str, k:int)->int:
vowel ={'a','e','i','o','u'}
prefix =[0]*(len(s)+1)for i inrange(len(s)):
prefix[i +1]= prefix[i]+(1if s[i]in vowel else0)
res =0for i inrange(k,len(s)+1):
res =max(res, prefix[i]- prefix[i - k])return res
publicclassSolution{publicintmaxVowels(String s,int k){Set<Character> vowels =Set.of('a','e','i','o','u');int[] prefix =newint[s.length()+1];for(int i =0; i < s.length(); i++){
prefix[i +1]= prefix[i]+(vowels.contains(s.charAt(i))?1:0);}int res =0;for(int i = k; i <= s.length(); i++){
res =Math.max(res, prefix[i]- prefix[i - k]);}return res;}}
classSolution{public:intmaxVowels(string s,int k){
unordered_set<char> vowels ={'a','e','i','o','u'};
vector<int>prefix(s.size()+1,0);for(int i =0; i < s.size(); i++){
prefix[i +1]= prefix[i]+(vowels.count(s[i])?1:0);}int res =0;for(int i = k; i <= s.size(); i++){
res =max(res, prefix[i]- prefix[i - k]);}return res;}};
classSolution{/**
* @param {string} s
* @param {number} k
* @return {number}
*/maxVowels(s, k){const vowels =newSet(['a','e','i','o','u']);const prefix =newArray(s.length +1).fill(0);for(let i =0; i < s.length; i++){
prefix[i +1]= prefix[i]+(vowels.has(s[i])?1:0);}let res =0;for(let i = k; i <= s.length; i++){
res = Math.max(res, prefix[i]- prefix[i - k]);}return res;}}
funcmaxVowels(s string, k int)int{
vowels :=map[byte]bool{'a':true,'e':true,'i':true,'o':true,'u':true}
prefix :=make([]int,len(s)+1)for i :=0; i <len(s); i++{if vowels[s[i]]{
prefix[i+1]= prefix[i]+1}else{
prefix[i+1]= prefix[i]}}
res :=0for i := k; i <=len(s); i++{if prefix[i]-prefix[i-k]> res {
res = prefix[i]- prefix[i-k]}}return res
}
class Solution {funmaxVowels(s: String, k: Int): Int {val vowels =setOf('a','e','i','o','u')val prefix =IntArray(s.length +1)for(i in s.indices){
prefix[i +1]= prefix[i]+if(s[i]in vowels)1else0}var res =0for(i in k..s.length){
res =maxOf(res, prefix[i]- prefix[i - k])}return res
}}
classSolution{funcmaxVowels(_ s:String,_ k:Int)->Int{let vowels:Set<Character>=["a","e","i","o","u"]let chars =Array(s)varprefix=[Int](repeating:0, count: chars.count +1)for i in0..<chars.count {prefix[i +1]=prefix[i]+(vowels.contains(chars[i])?1:0)}var res =0for i in k...chars.count {
res =max(res,prefix[i]-prefix[i - k])}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Sliding Window
Intuition
Instead of storing prefix sums, we can maintain a running count of vowels in the current window. As we slide the window right:
Add 1 if the new character entering the window is a vowel.
Subtract 1 if the character leaving the window is a vowel.
This gives us O(n) time with O(1) extra space.
Algorithm
Initialize a vowel count cnt and result res to 0.
Use two pointers: l (left) starts at 0, r (right) iterates through the string.
For each character at r:
If it is a vowel, increment cnt.
If the window size exceeds k, check if s[l] is a vowel and decrement cnt if so, then increment l.
classSolution:defmaxVowels(self, s:str, k:int)->int:
vowel ={'a','e','i','o','u'}
l = cnt = res =0for r inrange(len(s)):
cnt +=1if s[r]in vowel else0if r - l +1> k:
cnt -=1if s[l]in vowel else0
l +=1
res =max(res, cnt)return res
publicclassSolution{publicintmaxVowels(String s,int k){Set<Character> vowels =Set.of('a','e','i','o','u');int l =0, cnt =0, res =0;for(int r =0; r < s.length(); r++){
cnt +=(vowels.contains(s.charAt(r))?1:0);if(r - l +1> k){
cnt -=(vowels.contains(s.charAt(l))?1:0);
l++;}
res =Math.max(res, cnt);}return res;}}
classSolution{public:intmaxVowels(string s,int k){
unordered_set<char> vowels ={'a','e','i','o','u'};int l =0, cnt =0, res =0;for(int r =0; r < s.length(); r++){
cnt +=(vowels.count(s[r])?1:0);if(r - l +1> k){
cnt -=(vowels.count(s[l++])?1:0);}
res =max(res, cnt);}return res;}};
classSolution{/**
* @param {string} s
* @param {number} k
* @return {number}
*/maxVowels(s, k){const vowels =newSet(['a','e','i','o','u']);let l =0,
cnt =0,
res =0;for(let r =0; r < s.length; r++){
cnt += vowels.has(s[r])?1:0;if(r - l +1> k){
cnt -= vowels.has(s[l++])?1:0;}
res = Math.max(res, cnt);}return res;}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
4. Sliding Window (Bit Mask)
Intuition
Checking if a character is a vowel using a set or string comparison can be optimized using a bitmask. We assign each letter a bit position (a=0, b=1, ..., z=25). The vowels form a constant bitmask. Checking if a character is a vowel becomes a single bitwise operation.
This is a micro-optimization but can improve cache performance and avoid hash lookups.
Algorithm
Create a bitmask with bits set for vowel positions: mask = (1 << 0) | (1 << 4) | (1 << 8) | (1 << 14) | (1 << 20) for a, e, i, o, u.
To check if character c is a vowel: (mask >> (c - 'a')) & 1.
Apply the same sliding window logic as before, using the bitmask for vowel checks.
classSolution:defmaxVowels(self, s:str, k:int)->int:defgetId(c):returnord(c)-ord('a')
mask =(1<< getId('a'))|(1<< getId('e'))| \
(1<< getId('i'))|(1<< getId('o'))| \
(1<< getId('u'))
l = cnt = res =0for r inrange(len(s)):
cnt +=((mask >> getId(s[r]))&1)if r - l +1> k:
cnt -=((mask >> getId(s[l]))&1)
l +=1
res =max(res, cnt)return res
publicclassSolution{publicintmaxVowels(String s,int k){int mask =(1<<('a'-'a'))|(1<<('e'-'a'))|(1<<('i'-'a'))|(1<<('o'-'a'))|(1<<('u'-'a'));int l =0, cnt =0, res =0;for(int r =0; r < s.length(); r++){
cnt +=(mask >>(s.charAt(r)-'a'))&1;if(r - l +1> k){
cnt -=(mask >>(s.charAt(l)-'a'))&1;
l++;}
res =Math.max(res, cnt);}return res;}}
classSolution{public:intmaxVowels(string s,int k){int mask =(1<<('a'-'a'))|(1<<('e'-'a'))|(1<<('i'-'a'))|(1<<('o'-'a'))|(1<<('u'-'a'));int l =0, cnt =0, res =0;for(int r =0; r < s.size(); r++){
cnt +=(mask >>(s[r]-'a'))&1;if(r - l +1> k){
cnt -=(mask >>(s[l]-'a'))&1;
l++;}
res =max(res, cnt);}return res;}};
classSolution{/**
* @param {string} s
* @param {number} k
* @return {number}
*/maxVowels(s, k){constgetId=(c)=>{return c.charCodeAt(0)-'a'.charCodeAt(0);};const mask =(1<<getId('a'))|(1<<getId('e'))|(1<<getId('i'))|(1<<getId('o'))|(1<<getId('u'));let l =0,
cnt =0,
res =0;for(let r =0; r < s.length; r++){
cnt +=(mask >>getId(s.charAt(r)))&1;if(r - l +1> k){
cnt -=(mask >>getId(s.charAt(l)))&1;
l++;}
res = Math.max(res, cnt);}return res;}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
Common Pitfalls
Forgetting to Handle the Initial Window
A common mistake is updating the result before the window reaches size k. This can cause the algorithm to report incorrect maximum values from incomplete windows. Always ensure the window has exactly k elements before comparing with the result.
Not Removing the Outgoing Element Correctly
When sliding the window, forgetting to subtract the vowel count of the element leaving the window (at position l) leads to an ever-increasing count. The window must maintain exactly k elements, so when adding a new element on the right, the leftmost element must be removed from the count if the window exceeds size k.
Case Sensitivity with Vowels
If the input can contain uppercase letters, failing to handle case sensitivity causes vowels like 'A' or 'E' to be missed. Either convert the string to lowercase before processing or include both cases in the vowel set.