Skip to content
Reliable Data Engineering
Practice problem medium sliding-windowhash-mapstrings
Practise with timer, notes and rubric

Longest Uniform Run With at Most k Replacements

Pattern: Sliding Window · Difficulty: Medium · Asked at: Google, Amazon, Uber

Classic version: LeetCode 424

Problem

Given a string s of uppercase letters and an integer k, you may replace at most k characters with any letter. Return the length of the longest substring that can be made of a single repeated letter.

Examples

longest_uniform("ABAB", 2)     → 4    # replace both A's (or both B's)
longest_uniform("AABABBA", 1)  → 4    # "AABA" → "AAAA"

Constraints

Starter code

def longest_uniform(s: str, k: int) -> int:
    pass

Hints

Hint 1

A window is fixable when window_length - count_of_most_common_letter ≤ k.

Hint 2

You don’t need to decrease max_freq when shrinking. A stale (too high) max_freq can only keep the window at its current size; it can never produce a wrong larger answer.

Where this shows up in data engineering

Think of “longest stretch of a sensor reading the same state if we tolerate k glitches” or “longest run of a dominant category allowing k exceptions”. Same window, different labels.

Solution

from collections import Counter

def longest_uniform(s: str, k: int) -> int:
    counts = Counter()
    left = max_freq = best = 0
    for right, ch in enumerate(s):
        counts[ch] += 1
        max_freq = max(max_freq, counts[ch])
        # too many chars to replace: slide (not shrink) the window by one
        if (right - left + 1) - max_freq > k:
            counts[s[left]] -= 1
            left += 1
        best = max(best, right - left + 1)
    return best

Tests

Your solution should pass these:

assert longest_uniform("ABAB", 2) == 4
assert longest_uniform("AABABBA", 1) == 4
assert longest_uniform("A", 0) == 1
assert longest_uniform("ABCDE", 0) == 1
assert longest_uniform("AAAA", 2) == 4
assert longest_uniform("ABBB", 0) == 3
assert longest_uniform("BAAAB", 2) == 5

Explanation

Validity test: length - max_freq = characters that are not the dominant letter = edits needed. Valid when ≤ k.

The subtle trick: when the window becomes invalid we move left by exactly one, so the window slides at its best size so far instead of shrinking. max_freq may be stale (higher than the true max after removals), but the answer only grows when a real max_freq beats the old one, so the result stays correct.

Complexity: O(n) time, O(26) space. Recomputing max(counts.values()) each step is also fine (O(26n)) and easier to explain if you’re unsure about the stale-max argument.

Follow-up questions

Explain why not decreasing max_freq is safe.

The answer is max over time of window size. The window only grows when max_freq increases to a value that makes a larger window valid. A stale max_freq only stops the window from shrinking below the best size already found, never reports a larger invalid window as the answer.

Lower-case and digits too, arbitrary alphabet?

Same algorithm with a dict counter; complexity stays O(n).