Longest run of one letter after k changes
Given an uppercase string s and an integer k, you may change at most
k characters into any other uppercase letter. Return the length of the longest substring you can
turn into a single repeated letter.
Examples
| Input | Output | Why |
|---|---|---|
s = "ABAB", k = 2 | 4 | Change both A's, or both B's. |
s = "AABABBA", k = 1 | 4 | "AABA" with the B changed, or "ABBB" with the A changed. |
s = "ABC", k = 0 | 1 |
Constraints
- 1 ≤ |s| ≤ 10⁵
- s is uppercase English letters
- 0 ≤ k ≤ |s|
Hints
Hint 1
Which letter should a window become? The one it already holds most of.
Hint 2
A window can be fixed with (length − count of its most common letter) changes.
Hint 3
Keep counts and the largest count seen. When the window needs more than k changes, move the left end by one. Why is it safe never to lower the largest count?
Where people lose marks
- Choosing the letter to keep in advance. Each window keeps its own most common letter.
- Reading the window's contents as the answer. With a never-lowered
topthe window can hold a substring that is not fixable at that moment; only its length is meaningful. - Returning
top + k: it can exceed the length of the string.
Dry run on "AABABBA",1
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 7sA0lo/hiA1B2A3B4B5A6top1length1
'A' enters. The largest count seen is 1; the window of length 1 needs 0 changes.
-
Step 2 / 7sA0loA1hiB2A3B4B5A6top2length2
'A' enters. The largest count seen is 2; the window of length 2 needs 0 changes.
-
Step 3 / 7sA0loA1B2hiA3B4B5A6top2length3
'B' enters. The largest count seen is 2; the window of length 3 needs 1 change.
-
Step 4 / 7sA0loA1B2A3hiB4B5A6top3length4
'A' enters. The largest count seen is 3; the window of length 4 needs 1 change.
-
Step 5 / 7sA0A1loB2A3B4hiB5A6top3length4
'B' enters. The largest count seen is 3; the window of length 5 needs 2 changes. That is more than 1, so slide: drop 'A' and keep the length at 4.
-
Step 6 / 7sA0A1B2loA3B4B5hiA6top3length4
'B' enters. The largest count seen is 3; the window of length 5 needs 2 changes. That is more than 1, so slide: drop 'A' and keep the length at 4.
-
Step 7 / 7sA0A1B2A3loB4B5A6hitop3length4
'A' enters. The largest count seen is 3; the window of length 5 needs 2 changes. That is more than 1, so slide: drop 'B' and keep the length at 4. The window never shrank, so its final length, 4, is the answer.
How to think about it
A window can be turned into one repeated letter by changing every letter that is not its most common
one, so it needs length − top changes, where top is the count of its most common
letter. The window is valid when that is at most k, and validity survives shrinking, so the variable window
applies.
The trick is in maintaining top. Recomputing it after every removal costs 26 steps, which is
fine. But there is a neater argument: the answer only improves when a window holds a letter more times than
any window before it. So keep top as the largest count ever seen and never lower it. With a stale
top the window may be invalid for a while, but it never shrinks, it only slides at its best
length so far; and it can only grow when some letter genuinely reaches a new largest count. The length of the
window at the end is the answer.
Complexity. Time O(n) · Space O(1), 26 counts
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def longest_repeat(s, k):
count = [0] * 26
lo = top = 0
for hi, ch in enumerate(s):
count[ord(ch) - 65] += 1
top = max(top, count[ord(ch) - 65]) # the largest count ever seen; never lowered
if hi - lo + 1 - top > k: # too many changes: slide, do not shrink
count[ord(s[lo]) - 65] -= 1
lo += 1
return len(s) - lo#include <string>
#include <array>
#include <algorithm>
int solve(const std::string& s, int k) {
std::array<int, 26> count{};
int lo = 0, top = 0;
for (int hi = 0; hi < (int)s.size(); ++hi) {
top = std::max(top, ++count[s[hi] - 'A']); // the largest count ever seen; never lowered
if (hi - lo + 1 - top > k) { // too many changes: slide, do not shrink
--count[s[lo] - 'A'];
++lo;
}
}
return (int)s.size() - lo;
}class Solution {
public int longestRepeat(String s, int k) {
int[] count = new int[26];
int lo = 0, top = 0;
for (int hi = 0; hi < s.length(); hi++) {
top = Math.max(top, ++count[s.charAt(hi) - 'A']); // the largest count ever seen
if (hi - lo + 1 - top > k) { // too many changes: slide
count[s.charAt(lo) - 'A']--;
lo++;
}
}
return s.length() - lo;
}
}function longestRepeat(s, k) {
const count = new Array(26).fill(0);
let lo = 0, top = 0;
for (let hi = 0; hi < s.length; hi++) {
const i = s.charCodeAt(hi) - 65;
top = Math.max(top, ++count[i]); // the largest count ever seen; never lowered
if (hi - lo + 1 - top > k) { // too many changes: slide, do not shrink
count[s.charCodeAt(lo) - 65]--;
lo++;
}
}
return s.length - lo;
}fn solve(s: &str, k: usize) -> usize {
let b = s.as_bytes();
let mut count = [0usize; 26];
let (mut lo, mut top) = (0usize, 0usize);
for hi in 0..b.len() {
let i = (b[hi] - b'A') as usize;
count[i] += 1;
top = top.max(count[i]); // the largest count ever seen; never lowered
if hi + 1 - lo - top > k { // too many changes: slide, do not shrink
count[(b[lo] - b'A') as usize] -= 1;
lo += 1;
}
}
b.len() - lo
}import qualified Data.Map.Strict as M
-- Fold carrying (counts, letters still in the window from its left end, window length, top).
-- The window slides rather than shrinks, so its final length is the answer.
solve :: String -> Int -> Int
solve s k = len
where
(_, _, len, _) = foldl step (M.empty, s, 0, 0) s
step (m, win, n, top) c =
let m1 = M.insertWith (+) c 1 m
top' = max top (m1 M.! c)
n1 = n + 1
in if n1 - top' > k
then case win of
(x:xs) -> (M.adjust (subtract 1) x m1, xs, n, top')
[] -> (m1, win, n1, top')
else (m1, win, n1, top')
Variants to try
- Return which letter and where: record the window and its most common letter when the length grows.
- Lowercase and uppercase allowed: the alphabet grows to 52; nothing else changes.
Next problems
Longest run of tasks within a time budget
With positive numbers, a run that is over budget only gets worse as it grows, so the left end never moves back.
Open the problem → Medium15 minLongest run of distinct characters
The window that grows on the right and shrinks on the left, and the invariant that makes it correct.
Open the problem → Medium20 minLongest run with at most k distinct letters
The variable window in its plainest form: grow on the right, and while the window breaks the rule, shrink on the left.
Open the problem →Write a solution and run it against the real test table.
Keep going
Longest run of tasks within a time budget
With positive numbers, a run that is over budget only gets worse as it grows, so the left end never moves back.
Open the problem → Medium15 minLongest run of distinct characters
The window that grows on the right and shrinks on the left, and the invariant that makes it correct.
Open the problem → Medium20 minLongest run with at most k distinct letters
The variable window in its plainest form: grow on the right, and while the window breaks the rule, shrink on the left.
Open the problem → Sliding windowThe pattern behind it
A window that grows on the right, shrinks on the left, and holds an invariant at all times.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.