Longest Repeating Character Replacement
You get a string s of uppercase English letters and an integer k. You may pick at most k positions of s and change the letter at each one to any other uppercase letter.
Return the length of the longest substring, a run of letters that sit next to each other, that holds a single repeated letter after your changes.
Function
- sstring
- the string of uppercase letters
- kinteger
- the most letters you may change
- Returnsinteger
- the length of the longest substring of one repeated letter you can make
Constraints
1 ≤ s.length ≤ 5 × 104sholds only uppercase English letters.0 ≤ k ≤ s.length
Examples
- Input
- s = "BAAACAB"k = 1
- Output
- 5
- Explanation
- Changing the
CtoAmakes indices 1 to 5 readAAAAA. Both six-letter windows hold aBand theC, so they need two changes.
- Input
- s = "AABBBAB"k = 2
- Output
- 6
- Explanation
- Indices 1 to 6 hold
ABBBAB, and changing bothAletters givesBBBBBB. The whole string has only fourBletters among seven, so it needs three changes.
- Input
- s = "WXYZ"k = 0
- Output
- 1
- Explanation
- With no changes allowed, the answer is the longest run already in the string. Every letter differs from its neighbors, so that run is one letter.
Follow-up
What changes if s can hold any character instead of the 26 uppercase letters?
Hints
Open them one at a time. Each one gives away a little more.
For one fixed substring, which letter should every other letter turn into, and how many changes does that cost?
A substring is reachable when its length minus the count of its most common letter is at most
k. Find the longest window that meets that rule by moving two edges forward over the string.Keep 26 counts and the highest count
top. Add one letter on the right; if the window now needs more thankchanges, drop one letter on the left so the length stays the same. The window never has to shrink, andtopnever has to go down.
Solution
Keep a substring's most common letter and change the rest: a substring of length len whose top letter appears top times needs len - top changes. A sliding window finds the longest substring with len - top ≤ k in one pass, because the window never has to shrink and top never has to go down.
Check every substring
Correct, but does not finish on the largest tests
Intuition
For each start, grow the end one letter at a time, update a count per letter and top, and test len - top ≤ k. Each new substring costs one update instead of a fresh count, and every substring gets checked, so the longest reachable one cannot be missed.
A string of length n has about n²/2 substrings: 1.25 × 10^9 checks for n = 5 × 10^4.
Algorithm
- Set
bestto 0. - For every start index, reset the 26 counts and
topto 0. - Move
endfrom the start to the last index. Adds[end]to its count and raisetopif that count is now the highest. - If
end - start + 1 - top ≤ k, store the length if it beatsbest. - Return
best.
def characterReplacement(s, k):
n = len(s)
best = 0
for start in range(n):
count = [0] * 26 # letters in s[start..end]
top = 0 # count of the most common letter there
for end in range(start, n):
c = ord(s[end]) - ord('A')
count[c] += 1
top = max(top, count[c])
# Every letter that is not the most common one must change.
if end - start + 1 - top <= k:
best = max(best, end - start + 1)
return bestOne sliding window per target letter
Intuition
Choose the final letter first. If the run ends up all A, you want the longest substring with at most k letters that are not A.
Move right over the string and count the non-target letters in the window. When the count passes k, move left forward until it is back to k. A window that is too expensive stays too expensive when it grows, so left never moves back, and for each right the window is the longest good one ending there.
Run it for all 26 letters and keep the best: 26 linear passes over up to 5 × 10^4 letters. It stays O(n) only because the alphabet is small.
Algorithm
- For each target letter from
AtoZ, start a window withleft = 0andothers = 0. - Move
rightover the string. Ifs[right]is not the target, add one toothers. - While
others > k, moveleftforward, subtracting one when the letter that leaves is not the target. - Store
right - left + 1if it beatsbest. - After all 26 letters, return
best.
def characterReplacement(s, k):
best = 0
for target in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
left = 0
others = 0 # letters in s[left..right] that are not target
for right in range(len(s)):
if s[right] != target:
others += 1
# Too many letters to change: drop letters from the left.
while others > k:
if s[left] != target:
others -= 1
left += 1
best = max(best, right - left + 1)
return bestOne window that never shrinks
Intuition
Keep 26 counts for the window and top, the highest count. When adding s[right] makes the window too expensive, drop one letter on the left: the window slides and keeps its length, since only a longer window can beat the record. Otherwise it grows by one. Its length is always the best so far, so the answer is n - left.
When a letter leaves, leave top alone, even if it is now higher than the real count. After a slide the length is exactly top + k, so growing needs some letter to appear top + 1 times, and top rises with it at that moment. A stale top can only make the window slide, and a slide cannot lose the record.
In BAAACAB with k = 1, BAAAC needs 2 changes and slides to AAAC. The next A raises top to 4 and the window grows to AAACA, length 5.
Algorithm
- Keep 26 counts,
left = 0andtop = 0. - Move
rightover the string: adds[right]to its count and raisetopif that count is now higher. - If
right - left + 1 - top > k, removes[left]from the counts and moveleftone step. Never lowertop. - Return the final window length,
n - left.
def characterReplacement(s, k):
count = [0] * 26 # letters inside the window s[left..right]
left = 0
top = 0 # the highest count any letter has reached in a window
for right in range(len(s)):
c = ord(s[right]) - ord('A')
count[c] += 1
top = max(top, count[c])
# Needs more than k changes: slide the window instead of growing it.
if right - left + 1 - top > k:
count[ord(s[left]) - ord('A')] -= 1
left += 1
# The window only grew when a longer valid substring was found.
return len(s) - left
Pitfalls and edge cases
- Adding
kto the longest run. InAAABwithk = 3that gives 6, longer than the string. InBAAACABwithk = 1it gives 4, but changing theCjoins two runs into 5. - Counting changes against the window's first letter instead of its most common one.
BAAAneeds one change, not three. - Returning
n - leftfrom a loop that shrinks the window withwhileand recomputes the real maximum. That window gets shorter, so keep a separatebest. - Measuring the window as
right - left. Both ends are inside it, so add one.
Frequently asked questions3
What is the time complexity of Longest Repeating Character Replacement?
The one-window solution runs in O(n) time, where n is the length of s: right visits each letter once and left moves at most once per step. It uses O(1) extra space, 26 counts and a few integers.
How is this different from Longest Substring Without Repeating Characters?
Both move two edges over the string, but the rule for a good window differs. There, no character may repeat, so the window shrinks until the repeat is gone. Here, the length minus the top count must be at most k, so the window can slide at a fixed length.
Can this problem be solved with binary search?
Yes. If a substring of length L is reachable, so is every shorter one inside it, so you can binary search on L. For each L, slide a fixed window of that length and check whether any position needs at most k changes. That is O(n log n), slower than the one-window solution.
Similar problems
Problems that use the same ideas. Solving two or three of them is what makes a pattern stick.
def characterReplacement(s, k):
# Write code hereTest #1
Test #2
Test #3
Arguments
s = "BAAACAB" k = 1
Expected Output
5