Longest Repeating Character Replacement medium
Description
You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English character. You can perform this operation at most k times.
Return the length of the longest substring containing the same letter you can get after performing the operations.
Examples
> Case 1:
s = "ABAB", k = 2
Output: 4
Explanation: Replace the two 'A's with 'B' (or vice versa) to get "BBBB".
> Case 2:
s = "AABABBA", k = 1
Output: 4
Explanation: Replace one 'A' with 'B' to get "AABBBBA". Window "ABBB" or "BBBB" has length 4.Constraints
1 <= s.length <= 10^5sconsists of only uppercase English letters.0 <= k <= s.length
State design
The window s[l..r] is valid if we can make every character in it the same by changing at most k of them. Equivalently:
window_length − max_frequency_in_window ≤ kBecause the cheapest way to homogenize the window is to keep the most common character and change everything else.
- Contiguous range? Yes — substring.
- Monotone? Almost — when we add a character,
max_freqcan only go up or stay the same. Sowindow_length − max_freqcan only go up by at most 1 per step (length+1, max_freq+0 or +1). - State: a
freq[26]array, plus a runningmax_freq. - Shrink rule: while
(r − l + 1) − max_freq > k. - Record: after the shrink,
r − l + 1is a candidate.
The “we don’t need to update max_freq on shrink” trick
A subtle but beautiful detail: when we shrink from the left and decrement a character’s count, the true max_freq of the window might go down. But we don’t recompute it. Why is that OK?
The answer we care about is the longest valid window. Once we found a window of length L valid with max_freq = M, any smaller window can’t beat L. So if we lazily leave max_freq stale (potentially too high), the validity check length − max_freq ≤ k becomes too lenient, but it only fails to shrink when we should. The window length stays the same, so we never accidentally count a longer-than-real answer.
The result: the window length is monotonically non-decreasing across iterations — exactly what we need for the “longest” query.
Code
The most common confusion with this problem: “if the true max-frequency in the current window is lower than max_freq, doesn’t that invalidate our check?”
No — because max_freq is monotonically non-decreasing across the whole run (we only ever max(..., ...) it), and the answer is the max window length we’ve ever seen. A stale max_freq may let us hold the window a bit longer than the strict invariant requires, but it never lets us record a fake-larger answer. The numerical answer stays correct.
if vs while on the shrink
Many tutorials use if instead of while:
if (r - l + 1) - max_freq > k:
freq[ord(s[l]) - ord('A')] -= 1
l += 1This also works! Because we only ever expand by one per outer iteration, the window’s length grows by at most 1 per step, so a single shrink restores validity. Both versions are correct; while is safer style.
Analysis
- Time:
O(n). Each character enters and leaves at most once. - Space:
O(1)— fixed 26-entry frequency array.
Same skin
- Max Consecutive Ones III — same template, “k zeros allowed in window.”
- Longest Substring with At Most K Distinct Characters — variable-size with frequency map.
- Longest Subarray with Sum Divisible by K — different state (running sum mod K), prefix-sum cousin.