The problem
You get an uppercase string s and an integer k. You may change up to k characters to any other uppercase letter. Return the length of the longest substring that can be made of a single repeated letter.
Input: s = "AABABBA", k = 1
Output: 4Take the window "BABB" (indices 2 to 5) and change its single A to B, giving "BBBB", length 4. No length-5 window can be fixed with one change.
How to think about it
Start with brute force. For every substring, count its most common letter. The rest of the letters must be replaced, so the substring works if length − maxCount ≤ k. Trying all O(n²) substrings, even with running counts, is O(n²).
The key insight: the validity rule is a budget. For a window, the cheapest fix is to keep its most frequent letter and change everything else. So a window is valid exactly when (window length) − (count of its most frequent letter) ≤ k. That’s a condition you can check in O(1) as the window grows, and repair by shrinking from the left. That’s the variable sliding window.
A subtle trick: maxf never needs to go down. The code keeps maxf, the highest single-letter count seen in any window so far, and doesn’t lower it when the window shrinks. Why is that safe? The answer can only improve when some window has a bigger maxf than before. A stale, too-high maxf might let an invalid window survive, but such a window is never longer than the best valid one already recorded, because the window only shifts (grows by one, shrinks by one) in that state. So the answer stays correct, and you avoid rescanning all 26 counts.
Which pattern is this? A variable-size sliding window with a frequency map. The tell is “longest substring where at most k things are wrong”.
Approach
- Keep
cnt(letter counts in the window),start = 0,maxf = 0,best = 0. - For each
i, adds[i]tocntand updatemaxf. - If
(i - start + 1) - maxf > k, the window is over budget: removes[start]fromcntand movestartright by one. - Update
bestwith the current window length. - Return
best.
Solution
from collections import defaultdict
def char_replacement(s, k):
cnt = defaultdict(int); start = 0; best = 0; maxf = 0
for i, c in enumerate(s):
cnt[c] += 1; maxf = max(maxf, cnt[c]) # top count ever seen
if (i - start + 1) - maxf > k: # too many to replace
cnt[s[start]] -= 1; start += 1 # slide left edge by one
best = max(best, i - start + 1)
return best
# char_replacement("AABABBA", 1) -> 4Dry run
On s = "AABABBA", k = 1:
i |
c |
maxf |
Shrink? | Window | best |
|---|---|---|---|---|---|
| 0 | A | 1 | no | A | 1 |
| 1 | A | 2 | no | AA | 2 |
| 2 | B | 2 | no | AAB | 3 |
| 3 | A | 3 | no | AABA | 4 |
| 4 | B | 3 | yes | ABAB | 4 |
| 5 | B | 3 | yes | BABB | 4 |
| 6 | A | 3 | yes | ABBA | 4 |
The answer is 4.
Complexity
- Time: O(n). Each character enters the window once and leaves at most once.
- Space: O(26) = O(1). The count map holds at most 26 letters.
Edge cases and pitfalls
k = 0: the answer is the longest run of one letter already ins.k >= len(s): every window is valid, so the answer islen(s).- Recomputing
maxfon shrink withmax(cnt.values())is also correct and still O(26·n). It’s a fine answer if you can’t justify the stalemaxftrick in the interview. - Using
whileinstead ofifto shrink works too here, butifis enough because the window never needs to shrink by more than one step at a time. - Counting replacements of a fixed target letter misses the point: the letter to keep is whichever is most common in the current window.
Related problems
- Max Consecutive Ones III (LeetCode 1004): the binary version, where the budget is “at most k zeros”.
- Longest Substring Without Repeating Characters (LeetCode 3): the same longest-window template with a different validity rule.
- Fruit Into Baskets (LeetCode 904): a window limited by the number of distinct values.
- 1.Best Time to Buy and Sell Stock (LeetCode 121)
- 2.Maximum Average Subarray I (LeetCode 643)
- 3.Longest Substring Without Repeating Characters (LeetCode 3)
- 4.Longest Repeating Character Replacement (LeetCode 424)
- 5.Permutation in String (LeetCode 567)
- 6.Find All Anagrams in a String (LeetCode 438)
- 7.Fruit Into Baskets (LeetCode 904)
- 8.Max Consecutive Ones III (LeetCode 1004)
- 9.Minimum Size Subarray Sum (LeetCode 209)
- 10.Longest Subarray of 1's After Deleting One Element (LeetCode 1493)
- 11.Sliding Window Maximum (LeetCode 239)
- 12.Minimum Window Substring (LeetCode 76)