Problem
Given a string of uppercase English letters and an integer k, find the length of the longest substring you can turn into a single repeating character by changing at most k of its characters to anything you like.
- Input:
s = "AABABBA",k = 1 - Output:
4 - Explanation: Replace one 'B' in "AABA" (indices 0โ3) to get "AAAA", which has length 4.
Counter-example: s = "ABCDE", k = 1 โ output 2 โ any three-character window has at least two distinct characters requiring two replacements, which exceeds k=1.
Intuition
A window can be turned into a single repeating character if and only if the number of non-majority characters is at most k โ that is, window_size - max_frequency โค k. To maximize the window, we only care about making it larger than the best we've seen, so max_count (the highest character frequency encountered so far) never needs to decrease: a smaller max would only shrink the valid window, which is never useful.
Approach 1 โ Brute Force
Try every pair of indices as window boundaries, maintaining a frequency array as the right pointer extends, and check the replacement condition at each step.
- For each starting index
left, reset a fresh frequency array andmax_count. - Extend
rightone step at a time, incrementing the new character's frequency. - Update
max_countwith the new character's frequency. - If
(right - left + 1) - max_count <= k, the window is valid โ update the result. - Repeat until
rightreaches the end, then advanceleft.
1def characterReplacement(s: str, k: int) -> int:
2 result = 0
3 for left in range(len(s)):
4 char_count = [0] * 26
5 max_count = 0
6 for right in range(left, len(s)):
7 char_count[ord(s[right]) - ord('A')] += 1
8 max_count = max(max_count, char_count[ord(s[right]) - ord('A')])
9 if (right - left + 1) - max_count <= k:
10 result = max(result, right - left + 1)
11 return result- Time: O(nยฒ) โ all pairs of window boundaries, each O(1) step thanks to the incremental frequency update
- Space: O(1) โ fixed 26-element frequency array regardless of input length
Approach 2 โ Sliding Window
Maintain a window that only grows or shifts โ it never truly shrinks. max_count is kept at its historical peak because we only care about finding a window longer than the current best; a lower max would just mean a smaller valid window, which is useless.
- Track a 26-element frequency array and
max_countโ the highest character frequency seen in any valid window so far. - Extend
rightone step at a time, incrementing the new character's frequency. - Update
max_countwith the new character's frequency. - If
(window_size - max_count) > k, the window needs too many replacements โ shiftleftforward by one and decrement that character's count. Useif, notwhile: shifting more than once would shrink the window below its previous maximum, which is never beneficial. - After the loop, the window size is
len(s) - left.
1def characterReplacement(s: str, k: int) -> int:
2 char_count = [0] * 26
3 max_count = 0 # highest frequency seen in any valid window; intentionally never decremented
4 left = 0
5
6 for right in range(len(s)):
7 char_count[ord(s[right]) - ord('A')] += 1
8 max_count = max(max_count, char_count[ord(s[right]) - ord('A')])
9
10 # shift window by one without shrinking when replacements exceed budget
11 if (right - left + 1) - max_count > k:
12 char_count[ord(s[left]) - ord('A')] -= 1
13 left += 1
14
15 return len(s) - left- Time: O(n) โ each character is touched at most twice as
rightandlefteach traverse the string once - Space: O(1) โ fixed 26-element frequency array
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute Force | O(nยฒ) | O(1) | Verifying correctness on small inputs; establishing the core condition in an interview explanation |
| Sliding Window | O(n) | O(1) | Always โ linear time with no extra space is the canonical solution |
Common Mistakes
- Decrementing
max_countwhen shifting the left pointer โmax_countmust stay at its peak across all windows seen so far; lowering it would allow a smaller valid window to pass as if it were an improvement, corrupting the result. - Using
whileinstead ofifto shrink the window โ repeatedly shrinking until the window is valid again is correct logically, but it allows the window to become smaller than the best seen, costing O(n) extra work per step and eliminating the O(n) guarantee. - Returning
right - left + 1inside the loop โ the final window after the last iteration is the answer; readlen(s) - left(orright - left + 1at the end of the loop body) rather than tracking a separateresultvariable, or trackresultby updating it inside the loop before the shift check. - Assuming replacements must be contiguous โ the k replacements can be spread anywhere in the window; "ABBA" with k=2 โ "AAAA" is valid even though the two A's aren't adjacent, because we're replacing both B's independently.
- Using a hash map instead of a fixed array โ the problem guarantees uppercase English letters only, so a 26-element array indexed by
char - 'A'is both simpler and faster than a dictionary.
Related Problems
longest-substring-without-repeating-charactersโ same expand-then-shrink sliding window skeleton, but the validity condition is all-unique instead of at-most-k-replacementspermutation-in-stringโ fixed-size sliding window with character frequency matching; teaches the same 26-element array approachfind-all-anagrams-in-a-stringโ identical fixed-size frequency window, but collecting all match positions instead of the longest spanminimum-window-substringโ variable-size window that shrinks to minimize length rather than expanding to maximize it; the structural inverse of this problemmax-consecutive-ones-iiiโ identical algorithmic structure with a binary alphabet: flip at most k zeros, find the longest all-ones window