Back to Strings
Q25Question 25 of 61

Longest Repeating Character Replacement: Window Where size − maxFreq ≤ K

Key Concepts
  • A window is valid when its size minus the highest character frequency is at most K, meaning at most K non-dominant characters need replacing.
  • The maxFreq value is a non-decreasing high-water mark that intentionally never resets when characters leave the window.
  • The window size is monotonically non-decreasing because only a single-step shrink is applied per expansion.
  • A stale maxFreq cannot produce a wrong larger answer because the best answer only grows with a genuinely higher frequency.
  • Time complexity is O(n) with O(1) space using a fixed-size 26-element frequency array.
  • When K equals zero, the answer is the longest existing run of identical characters.

Definition

A window is valid when the number of characters to replace, its size minus the most frequent character's count, is at most K.
  • Validity rule is windowSize - maxFreq <= K, where maxFreq is the count of the most frequent character in the window.
  • Non-decreasing maxFreq is the famous trick: the tracked maximum frequency never decreases even when characters leave, which is safe because the answer only grows with a genuinely larger window.
  • Single-step shrink advances the left pointer by at most one when the condition is violated, keeping the window monotonically non-decreasing.
  • Frequency array of size 26 tracks character counts with O(1) lookups.
  • O(n) time because both pointers traverse the string once and no inner loop rescans frequencies.

The National Bank of India stores daily account status codes as a string and wants the longest stretch that could be made uniform by correcting at most K mis-tagged entries. Rajesh grows a window, notes which status appears most, and if the number of non-dominant entries exceeds K, he shrinks from the left by exactly one to maintain the window's validity.

Understanding the Concepts

What is the problem?

Given a string of uppercase letters and an integer K, find the length of the longest substring you can make uniform by replacing at most K characters. This is the same problem as Q1446, presented with a focus on the non-decreasing maxFreq invariant and why the single-step shrink keeps the algorithm correct.

JAVA
package com.bank.dsa;

public class CharReplacementV2 {
    public static int characterReplacement(String s, int k) {
        int[] count = new int[26];
        int left = 0, maxFreq = 0, best = 0;
        for (int right = 0; right < s.length(); right++) {
            count[s.charAt(right) - 'A']++;
            maxFreq = Math.max(maxFreq, count[s.charAt(right) - 'A']);
            if ((right - left + 1) - maxFreq > k) {
                count[s.charAt(left) - 'A']--;
                left++;
            }
            best = Math.max(best, right - left + 1);
        }
        return best;
    }
}

Why does the non-decreasing maxFreq work?

This is the most asked follow-up. When a character leaves the window, its count decreases, so the true maximum frequency might drop. But we deliberately keep maxFreq at its highest value ever seen. The key insight is that the best answer can only increase when a larger window is found valid, and a larger window requires a genuinely higher frequency. A stale maxFreq might let the current window appear valid at sizes we already achieved, but it cannot create a new best that is wrong.

JAVA
package com.bank.dsa;

public class MaxFreqInvariantDemo {
    public static void main(String[] args) {
        String s = "AABBC";
        int k = 1;
        int[] count = new int[26];
        int left = 0, maxFreq = 0, best = 0;
        for (int right = 0; right < s.length(); right++) {
            count[s.charAt(right) - 'A']++;
            maxFreq = Math.max(maxFreq, count[s.charAt(right) - 'A']);
            int windowSize = right - left + 1;
            boolean valid = (windowSize - maxFreq) <= k;
            if (!valid) {
                count[s.charAt(left) - 'A']--;
                left++;
                windowSize--;
            }
            best = Math.max(best, right - left + 1);
            System.out.println("right=" + right + " maxFreq=" + maxFreq
                + " windowSize=" + (right - left + 1) + " valid=" + valid + " best=" + best);
        }
    }
}
TEXT
right=0 maxFreq=1 windowSize=1 valid=true best=1
right=1 maxFreq=2 windowSize=2 valid=true best=2
right=2 maxFreq=2 windowSize=3 valid=true best=3
right=3 maxFreq=2 windowSize=3 valid=false best=3
right=4 maxFreq=2 windowSize=3 valid=false best=3

Why does single-step shrink work?

Since the window grew by one this step (right moved forward), shrinking by at most one from the left keeps the window size from ever decreasing. The window is effectively maintained at a floor equal to the best size found so far. Once you have found a window of size 4, the window never shrinks below 4; it stays the same or grows.

JAVA
package com.bank.dsa;

public class MonotonicWindowDemo {
    public static void main(String[] args) {
        String s = "AABABBA";
        int k = 1;
        int[] count = new int[26];
        int left = 0, maxFreq = 0;
        for (int right = 0; right < s.length(); right++) {
            count[s.charAt(right) - 'A']++;
            maxFreq = Math.max(maxFreq, count[s.charAt(right) - 'A']);
            if ((right - left + 1) - maxFreq > k) {
                count[s.charAt(left) - 'A']--;
                left++;
            }
            System.out.println("Window size: " + (right - left + 1));
        }
        // Window size never decreases
    }
}
TEXT
Window size: 1
Window size: 2
Window size: 3
Window size: 4
Window size: 4
Window size: 4
Window size: 4

What is the time and space complexity?

Time is O(n) because the right pointer moves once through the string and the left pointer moves forward at most once per step, with no inner loop. Space is O(1) with a fixed 26-element frequency array for uppercase letters.

What are the edge cases?

When K is 0, no replacements are allowed, so the answer is the longest run of identical characters. When K is greater than or equal to the string length, the entire string can be made uniform. When all characters are already the same, the answer is the string length regardless of K.

JAVA
package com.bank.dsa;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        System.out.println(CharReplacementV2.characterReplacement("AABABBA", 0)); // 2
        System.out.println(CharReplacementV2.characterReplacement("ABC", 3));     // 3
        System.out.println(CharReplacementV2.characterReplacement("AAAA", 0));    // 4
        System.out.println(CharReplacementV2.characterReplacement("", 5));         // 0
    }
}
TEXT
2
3
4
0

What are common mistakes?

The first mistake is trying to decrease maxFreq, which either adds an O(26) recomputation loop or breaks the invariant. The second mistake is using a while loop to shrink multiple times when one step is sufficient. The third mistake is checking the validity condition after updating best instead of before.

This problem is harder than the basic unique-substring window because the validity condition involves frequency tracking and the non-decreasing maxFreq trick. The minimum window substring problem uses a similar frequency map but has a different structure: it minimizes the window rather than maximizing it.

What are common interview follow-up questions?

  • What if the string contains both uppercase and lowercase letters?
  • How would you return the actual longest substring?
  • What if K can be negative (you must keep certain characters unchanged)?
  • How does this generalize to the longest substring with at most K distinct characters?

Visual Overview

DIAGRAM
  ┌────────────────────────────────────────────────┐
  │  Validity Condition                             │
  │                                                 │
  │  windowSize - maxFreq <= K                      │
  │                                                 │
  │  Window: [A, A, B, A]  size=4                   │
  │  Frequencies: A=3, B=1  maxFreq=3               │
  │  Replacements: 4 - 3 = 1  <= K=1 ── VALID      │
  └────────────────────────────────────────────────┘

  ┌────────────────────────────────────────────────┐
  │  maxFreq High-Water Mark                        │
  │                                                 │
  │  maxFreq only goes UP, never DOWN               │
  │  This is SAFE because:                          │
  │  - best only grows with larger valid window     │
  │  - larger window needs genuinely higher freq    │
  │  - stale maxFreq cannot inflate best            │
  └────────────────────────────────────────────────┘

  ┌────────────────────────────────────────────────┐
  │  Monotonic Window Size                          │
  │                                                 │
  │  right grows by 1 each step                     │
  │  left grows by at most 1 each step              │
  │  So: windowSize either stays or grows            │
  └────────────────────────────────────────────────┘

Key Takeaway

The Longest Repeating Character Replacement with the non-decreasing maxFreq invariant is one of the subtlest sliding window problems. Rajesh at the National Bank of India uses it to measure how clean an account status history could be made by correcting at most K entries. The validity condition is windowSize - maxFreq <= K, meaning the non-dominant characters that would need replacing do not exceed the budget.

The famous trick is that maxFreq never decreases even when characters leave the window. This looks like a bug but is deliberately correct. The best answer only grows when a larger window is found valid, and a larger valid window requires a genuinely higher maximum frequency. A stale maxFreq can only let the window appear valid for sizes already achieved, never produce a wrong new best.

The single-step shrink keeps the window size monotonically non-decreasing. Since the window grew by one from the right, shrinking by at most one from the left means the window never gets smaller than its previous best. Combined with the non-decreasing maxFreq, this gives a clean O(n) algorithm with O(1) space over a fixed alphabet.

Understanding why a stale maxFreq is safe and why the single-step shrink maintains the invariant is what interviewers are testing with this problem. It is harder than the basic distinct-substring window and shows mastery of the sliding window technique.

Remember one sentence: the window is valid when size minus the dominant character's count is at most K, and the non-decreasing maxFreq trick keeps it linear and correct.

---

Share
Use ← → arrow keys to navigate