Solving tips
- This is a pure fixed-size window: count vowels in the first k chars, then slide updating by +1 if the entering char is a vowel and -1 if the leaving one is.
- Use a set/frozenset of 'aeiou' for O(1) vowel tests; the window length never changes so there is no shrink loop.
- Target O(n) time and O(1) space; optional early exit once the count reaches k since no window can beat it.
- Common pitfall: the leaving index is s[right-k] (not right-k+1), and do not recount the whole slice each step or you silently reintroduce O(n*k).
Problem
Given a lowercase string s and an integer k, look at every contiguous substring of s of length exactly k and return the largest number of vowels (a, e, i, o, u) any of them contains.
The answer is a count, not the substring itself. If s contains no vowels at all, the answer is 0.
Examples
s = "abciiidef", k = 3 β 3 β the window "iii" is all vowels.
s = "aeiou", k = 2 β 2 β every length-2 window ("ae", "ei", β¦) is two vowels.
s = "rhythm", k = 4 β 0 β no vowels anywhere, so every window scores zero.
Constraints
1 <= len(s) <= 10^5
1 <= k <= len(s)
s consists of lowercase English letters only.
Counting vowels from scratch in each of the n - k + 1 windows costs O(nΒ·k) β up to ~10^9β10^10 checks. The expected solution updates the count in O(1) per slide.
Think about it first
Hint 1
The windows all have the same length and adjacent ones overlap in k - 1 characters. Do you really need to recount the shared part?
Hint 2
When the window slides one step, exactly one character enters on the right and one leaves on the left. How does each affect the vowel count?
Hint 3
Count vowels in the first k characters. Then for each slide: add 1 if the entering character is a vowel, subtract 1 if the leaving one is. Track the maximum; a set of the five vowels makes each test O(1). (Nice micro-exit: if the count ever reaches k, no window can beat it.)
TL;DR
Fixed-size sliding window with an incrementally maintained vowel count β O(n) time, O(1) space.
Approach 1 β Brute force
Count vowels independently in every length-k window.
class Solution:
def maxVowels(self, s: str, k: int) -> int:
vowels = set("aeiou")
best = 0
for i in range(len(s) - k + 1):
count = 0
for ch in s[i:i + k]:
if ch in vowels:
count += 1
best = max(best, count)
return best
Complexity: O(nΒ·k) time, O(k) space for the slice. With n = 10^5 and large k, roughly 10^9β10^10 membership tests β over budget, and almost all of it is recounting characters the previous window already counted.
Approach 2 β Sliding window count
The insight: sliding a fixed window one step right changes its contents by exactly two characters β one in, one out. The vowel count therefore changes by at most 1 in each direction, so after an O(k) initialization every window is scored in O(1). (This is the general fixed-window rule: maintain the aggregate incrementally whenever the aggregate supports O(1) add/remove.)
class Solution:
def maxVowels(self, s: str, k: int) -> int:
vowels = frozenset("aeiou")
count = 0
for ch in s[:k]:
if ch in vowels:
count += 1
best = count
for right in range(k, len(s)):
if s[right] in vowels: # entering character
count += 1
if s[right - k] in vowels: # leaving character
count -= 1
best = max(best, count)
if best == k: # optional early exit: cannot do better
return best
return best
Walkthrough on s = "abciiidef", k = 3:
| step | window | entering | leaving | count | best |
|---|
| init | "abc" | β | β | 1 | 1 |
| right=3 | "bci" | i (+1) | a (β1) | 1 | 1 |
| right=4 | "cii" | i (+1) | b (β0) | 2 | 2 |
| right=5 | "iii" | i (+1) | c (β0) | 3 | 3 |
best == k == 3 triggers the early exit; windows "iid", "ide", "def" are never scored. Answer: 3.
Complexity: O(n) time β one pass, O(1) work per character; O(1) space (the vowel set is constant-sized).
Common pitfalls
- Testing the wrong leaving index. When
s[right] enters, s[right - k] leaves β not right - k + 1. Sanity-check with k = 1: entering and leaving indices must be right and right - 1.
- Rebuilding the count per window (calling a count over the slice inside the loop) silently reintroduces the O(nΒ·k) cost even though the code βuses a window.β
- Fixed window means no shrink loop. If youβre writing
while here, youβve drifted into the variable-size template; this problem never asks whether the window is βvalidβ β every position is scored.
- Initializing
best = 0 is fine here (counts are non-negative), but initialize from the first windowβs count anyway β the habit saves you on problems where the aggregate can be negative.
Pattern takeaway
The fixed-size window template in three lines: build the first windowβs aggregate in O(k), then slide with +entering / -leaving, tracking the best. Recognize it whenever the problem fixes the window length (substring of length k, k consecutive days) β the only design decision left is choosing an aggregate that updates in O(1), here a vowel counter.