Skip to content

feat: solve Longest Repeating Character Replacement (#424)#225

Merged
pertrai1 merged 2 commits into
mainfrom
tdd/424-longest-repeating-character-replacement
Feb 7, 2026
Merged

feat: solve Longest Repeating Character Replacement (#424)#225
pertrai1 merged 2 commits into
mainfrom
tdd/424-longest-repeating-character-replacement

Conversation

@pertrai1

@pertrai1 pertrai1 commented Feb 7, 2026

Copy link
Copy Markdown
Owner

Problem

Longest Repeating Character Replacement — Medium

Approach

Variable-size sliding window with character frequency tracking. The key insight is that a window is valid when windowLength - maxFrequency ≤ k, meaning we can replace the non-max-frequency characters to make the window uniform.

Complexity

  • Time: O(n) - single pass with two pointers
  • Space: O(1) - frequency map has at most 26 uppercase letters (constant)

Pattern

Variable-Size Sliding Window with constraint tracking

TDD Summary

  • Cycles: 5 red-green-refactor cycles
  • Tests: 6 test cases covering all examples and edge cases
  • Key Insight: Window is valid when windowLength - maxFrequency ≤ k

Files

  • README.md — Problem statement (clean markdown)
  • longest-repeating-character-replacement.ts — Solution with O(n) sliding window algorithm
  • longest-repeating-character-replacement.test.ts — Comprehensive test suite (6 tests)
  • POST_MORTEM.md — Detailed analysis, pattern recognition, and retrospective

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added solution for LeetCode 424 (Longest Repeating Character Replacement).
  • Documentation

    • Added comprehensive problem README with description, examples, and constraints.
    • Added a post-mortem detailing approach, edge cases, and a sliding-window cheat-sheet.
  • Tests

    • Added test suite covering typical and edge case scenarios.
  • Chores

    • Updated solved problem counts in the main README.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@pertrai1 pertrai1 added the code challenge this is a coding challenge label Feb 7, 2026
@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

Claude finished @pertrai1's task in 1m 18s —— View job


Code Review Complete ✅

Review Checklist

  • Read all changed files
  • Verify algorithm correctness
  • Validate complexity analysis
  • Check test coverage
  • Identify patterns and key insights
  • Suggest related problems
  • Provide comprehensive review feedback

Overall Assessment

Excellent work! This is a high-quality solution with strong TDD discipline, accurate complexity analysis, and comprehensive documentation. The code is clean, well-tested, and demonstrates solid understanding of the sliding window pattern.

✅ Algorithm Correctness

The solution is correct and handles all test cases properly. I verified the logic:

  • Window validity formula (right - left + 1 - maxFreq > k) is accurate
  • Edge cases are properly handled (k=0, single char, all same chars, k > needed)
  • Boundary management is clean with no off-by-one errors

The algorithm correctly identifies that we only need windowLength - maxFrequency replacements to make all characters in the window the same.

✅ Complexity Analysis

Your complexity analysis is accurate:

  • Time: O(n) ✅ - Each character is visited at most twice (once by right pointer when added, once by left pointer when removed). This is true amortized linear time.
  • Space: O(1) ✅ - The charCount map has at most 26 entries (uppercase English letters), which is constant space regardless of input size.

Why O(n) and not O(26n)? The maxFreq tracking is clever - you maintain a running maximum without recalculating from scratch on every iteration. This eliminates the O(26) factor that would come from scanning the frequency map.

✅ Pattern Recognition

Pattern: Variable-Size Sliding Window with constraint tracking

You correctly identified the pattern and provided excellent explanation in the POST_MORTEM.md. The key insight is beautifully stated:

"A window is valid if and only if: window_length - max_frequency_in_window ≤ k"

This formula is the critical insight that makes the O(n) solution possible.

🎯 Key Insight Explained

The non-obvious trick here is that you don't need to recalculate maxFreq when shrinking the window. Here's why:

When you shrink the window by moving left forward, maxFreq might become stale (it might not reflect the true maximum in the current window). But this is intentional and correct!

  • If maxFreq is stale and higher than the true max, the window size won't grow
  • But we've already recorded that window size in maxLength
  • The algorithm is looking for the maximum valid window, so a stale high maxFreq only prevents us from accepting invalid windows as we continue

This subtle optimization keeps the time complexity at O(n) instead of O(26n).

✅ Test Coverage

Your test suite is comprehensive and covers:

  • ✅ Both example cases from problem statement
  • ✅ Edge case: k=0 with different characters → returns 1
  • ✅ Edge case: all same characters → returns full length
  • ✅ Edge case: single character string → returns 1
  • ✅ Edge case: k larger than needed → returns full length

One additional test to consider (minor suggestion):

it('should handle alternating pattern with k=1', () => {
  expect(characterReplacement('ABABAB', 1)).toBe(5);
  // Explanation: "ABABA" can become "AAAAA" with 2 replacements, 
  // but with k=1 we get "AAABA" (length 5)
});

This tests a longer repeating pattern, but your current coverage is already excellent.

💡 Code Quality

Strengths:

  • Clear, descriptive variable names (maxFreq, maxLength, left, right)
  • Well-structured comments explaining each step
  • Clean TypeScript typing with proper JSDoc
  • Follows modern ES6+ conventions

Minor observations:

  • The comment on line 21 says "need more than k replacements" - this is accurate and clear ✅
  • The frequency map initialization is clean with the || 0 pattern ✅
  • No issues found! This is production-ready code.

🔗 Related Problems for Practice

Your POST_MORTEM already lists excellent related problems. Here are my recommendations prioritized by similarity:

  1. LeetCode 1004: Max Consecutive Ones IIIMost Similar

    • Nearly identical structure: "Find longest subarray of 1s after flipping at most k zeros"
    • Same pattern, simpler problem (binary instead of 26 letters)
    • Great for reinforcing the pattern
  2. LeetCode 340: Longest Substring with At Most K Distinct Characters (Premium)

    • Variable sliding window with k constraint
    • Different constraint: number of distinct characters instead of replacements
  3. LeetCode 3: Longest Substring Without Repeating Characters

    • Classic sliding window problem
    • Good for practicing the window shrinking logic
  4. LeetCode 76: Minimum Window Substring

    • Advanced sliding window (Hard difficulty)
    • Similar frequency tracking, but finding minimum instead of maximum

📊 Alternative Approaches

You mentioned brute force in the POST_MORTEM. Here's one more variation worth knowing:

Optimized Sliding Window with Full maxFreq Recalculation (O(26n)):

// Instead of maintaining stale maxFreq, recalculate on each shrink
while (right - left + 1 - Math.max(...Object.values(charCount)) > k) {
  charCount[s[left]]--;
  left++;
}

Trade-offs:

  • ❌ Slower: O(26n) = O(n) but with higher constant factor
  • ✅ Easier to understand: maxFreq always reflects true maximum
  • ❌ Less efficient in practice

Your solution is better - the stale maxFreq optimization is clever and maintains true O(n) complexity.

🎓 Learning Summary

Key Concepts from This Problem:

  1. Variable Sliding Window Pattern: Expand right, shrink left when invalid
  2. Constraint Tracking: Formula windowLength - maxFreq ≤ k validates window
  3. Optimization Insight: Stale maxFreq is acceptable when finding maximum window
  4. Frequency Maps: Track character counts efficiently with Record<string, number>
  5. Amortized Analysis: Each element visited at most twice = O(n) total

Interview Insights:

  • Recognizing this as sliding window is the first breakthrough
  • Deriving the validity formula is the key insight
  • Understanding why stale maxFreq works shows deep algorithmic thinking
  • This pattern appears frequently in string/array problems with constraints

When to Use This Pattern:

  • Problems asking for "longest/shortest subarray/substring"
  • With a constraint that can be checked in O(1)
  • Where brute force would be O(n²) or O(n³)

Final Verdict

Grade: 5/5 ⭐⭐⭐⭐⭐

This solution demonstrates:

  • ✅ Correct algorithm with optimal O(n) time complexity
  • ✅ Excellent TDD discipline (5 red-green-refactor cycles)
  • ✅ Comprehensive test coverage (6 test cases)
  • ✅ Clear, well-documented code
  • ✅ Deep understanding of the sliding window pattern
  • ✅ Outstanding POST_MORTEM documentation

No changes needed. This is a model solution that other learners can reference. The TDD approach prevented bugs, the tests are thorough, and the documentation clearly explains the key insights.

Keep up the excellent work! 🚀


@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

✅ Test Results

Tests have been executed for this pull request.

📊 Coverage Summary

Metric Coverage
Statements 94.83%
Branches 89.4%
Functions 86.04%
Lines 94.89%

Check the workflow logs for detailed test output.

@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds a new solution for LeetCode 424 (Longest Repeating Character Replacement): a TypeScript sliding-window implementation with tests, README, and a detailed post-mortem documenting the invariant and approach.

Changes

Cohort / File(s) Summary
LeetCode 424 implementation & tests
leetcode/medium/0424-longest-repeating-character-replacement/longest-repeating-character-replacement.ts, leetcode/medium/0424-longest-repeating-character-replacement/longest-repeating-character-replacement.test.ts
Adds characterReplacement(s, k) using a variable-size sliding window and frequency map; tracks max frequency and shrinks when window_length - max_freq > k. Test suite (6 cases) covers typical and edge scenarios.
Problem docs & post-mortem
leetcode/medium/0424-longest-repeating-character-replacement/README.md, leetcode/medium/0424-longest-repeating-character-replacement/POST_MORTEM.md
Adds problem README with description, examples, constraints, and a detailed post-mortem explaining solution exploration, invariant, edge cases, and a cheat-sheet template.
Top-level README update
README.md
Increments solved counts and adds the new Medium problem entry (0424) under Medium / Sliding Window group.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through windows, wide and small,
Counting letters, chasing them all.
When replacements max out, I pull back tight,
Then leap ahead to find the longest sight. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: solve Longest Repeating Character Replacement (#424)' directly describes the main change—solving LeetCode problem 424 using a sliding window approach, which aligns with the changeset adding implementation, tests, README, and documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tdd/424-longest-repeating-character-replacement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

🤖 Gemini Code Review (Model: gemini-2.5-flash)

This is an excellent solution and a very thorough post-mortem! It demonstrates a strong understanding of the problem, the chosen algorithmic pattern, and best practices for development, including TDD.

Here's a detailed review:


1. Pattern Identification

The solution correctly uses the Variable-Size Sliding Window pattern. Specifically, it's a "Shrinking Window" variant where the window expands from the right and shrinks from the left only when a constraint is violated.

This pattern is ideal here because we are looking for the "longest substring" that satisfies a certain condition, and the condition (number of replacements needed) can be efficiently checked and updated as the window slides.

2. Complexity Verification

  • Time Complexity: O(n)
    • Stated Complexity: O(n)
    • Verification: Accurate. Both the left and right pointers traverse the string at most once. The right pointer iterates n times. The left pointer also moves forward at most n times across the entire execution. Operations inside the loop (hash map access, Math.max) are O(1). Therefore, the total time complexity is O(n).
  • Space Complexity: O(1)
    • Stated Complexity: O(1)
    • Verification: Accurate. The charCount hash map stores frequencies of uppercase English letters. Since there are only 26 possible uppercase English letters, the maximum size of the hash map is constant (26 entries). This is considered O(1) space complexity, regardless of the input string's length.

3. Key Insight

The key insight, as perfectly articulated in your POST_MORTEM.md, is:

A substring (window) is valid if window_length - max_frequency_in_window ≤ k.

This formula elegantly captures the problem's core. If we decide to convert all characters in a window to the most frequent character within that window, then window_length - max_frequency_in_window gives us the exact number of characters that need to be replaced. If this count is less than or equal to k, the window is valid.

The "aha!" moment is also realizing that when the window shrinks from the left, we don't necessarily need to recalculate maxFreq from scratch. The maxFreq we've tracked so far for the current window is sufficient. Why? Because we are only interested in finding the longest valid window. If maxFreq decreases when left moves, the current window_length - new_max_freq might become valid again, but a larger maxFreq from a previous, wider window (even if it's no longer present in the current exact window) would still have allowed a larger overall maxLength. The maxLength variable effectively stores the maximum length achieved by any valid window encountered, and this maximum maxFreq helps us maintain the condition for those potentially longer windows. This is a subtle but critical point for why maxFreq doesn't need to be updated downwards when left moves.

4. Edge Case Analysis

Your POST_MORTEM.md and test suite (longest-repeating-character-replacement.test.ts) demonstrate excellent edge case analysis.

  • k = 0 and all characters different ("ABCD", k=0 -> 1): Correctly returns 1, as no replacements are allowed, so the longest repeating character substring is a single character.
  • All characters the same ("AAAA", k=0 -> 4): Correctly returns the full length, as no replacements are needed.
  • Single character string ("A", k=1 -> 1): Correctly returns 1.
  • k is very large ("ABCDE", k=10 -> 5): Correctly returns the full string length, as k allows replacing all characters to form a single repeating character string.
  • Standard examples ("ABAB", k=2 -> 4; "AABABBA", k=1 -> 4): Both provided examples are handled correctly.

No apparent edge cases were missed. The test coverage is comprehensive and validates the solution across various scenarios.

5. Learning Points

  • Similar Problems using this pattern:

    • LeetCode 1004: Max Consecutive Ones III: This is almost identical, just with 0s and 1s instead of characters. The constraint is "at most k flips".
    • LeetCode 3: Longest Substring Without Repeating Characters: A classic sliding window problem where the constraint is "all characters in the window must be unique".
    • LeetCode 340: Longest Substring with At Most K Distinct Characters: Find the longest substring that contains at most k unique characters.
    • LeetCode 209: Minimum Size Subarray Sum: Another variable-size sliding window, but finding the minimum length subarray that meets a sum condition.
    • LeetCode 76: Minimum Window Substring: A more complex sliding window problem involving finding the smallest window containing all characters of another string.
  • Common Mistakes with this pattern:

    • Off-by-one errors: Incorrectly calculating window_length (e.g., right - left instead of right - left + 1).
    • Incorrect left pointer movement: Not shrinking the window when the constraint is violated, or shrinking it incorrectly (e.g., forgetting to decrement frequency).
    • Not updating maxFreq correctly: While maxFreq doesn't need to decrease when left moves in this specific problem, in other sliding window problems where maxFreq is part of the constraint (e.g., "at most K distinct characters"), it's crucial to update maxFreq or recalculate it for the current window. The nuance here is that maxFreq is used to determine how many replacements are needed if we pick the historically most frequent character in the current window's range.
    • Not handling empty string or single-character string edge cases: Though constraints usually specify s.length >= 1.
  • Variations of this problem:

    • Find the number of such substrings: Instead of the longest length, count how many valid substrings exist. This would require a slightly different approach to sum up valid window lengths.
    • Given a target length L, find the minimum k needed: This could involve binary searching on k or a different sliding window approach.
    • What if k can be different for different characters? (e.g., k_A replacements for 'A', k_B for 'B'). This would complicate the max_frequency logic significantly.
    • Input string contains lowercase and uppercase letters, or other characters: The charCount map would need to handle a larger character set, but the O(1) space complexity would still hold if the character set is fixed (e.g., ASCII).

6. Code Quality

The code quality is excellent:

  • Variable Naming: charCount, maxFreq, maxLength, left, right are all descriptive and follow common DSA conventions.
  • Code Structure: Clean, well-organized, and follows the standard sliding window template. The expansion and shrinking logic are clearly separated within the loop.
  • Readability: The code is highly readable. The use of charCount[s[right]] = (charCount[s[right]] || 0) + 1; is a concise and idiomatic way to handle frequency increments in JavaScript.
  • TypeScript Best Practices: Uses Record<string, number> for the frequency map, const for variables that aren't reassigned (except maxLength, maxFreq, left, which are correctly let). The JSDoc comment is clear and helpful.
  • Comments: The JSDoc comment for the function is great. Given the clarity of the variable names and the standard pattern, inline comments are not strictly necessary but could be added for the while loop condition to explicitly state "If window is invalid..." for maximum clarity, as seen in your POST_MORTEM.md template.

7. Alternative Approaches

  1. Brute Force (O(n^3) or O(n^2)):

    • Approach: Iterate through all possible substrings using nested loops (start index i, end index j). For each substring s[i...j], iterate again to count character frequencies and determine the max_frequency. Check if (j - i + 1) - max_frequency <= k.
    • Trade-offs: Simple to understand and implement but highly inefficient for larger strings.
      • Time: O(n^3) if counting frequencies for each substring, O(n^2) if max_frequency can be updated in O(1) from the previous substring (e.g., using a frequency array/map for each i, and updating it as j increases).
      • Space: O(1) or O(26).
  2. Binary Search on the Answer (O(n log n)):

    • Approach: The problem asks for the longest length. We can observe that if a substring of length L is possible, then any substring of length L-1 is also possible (trivial). This monotonicity allows us to binary search for the maximum possible length.
    • Check Function canAchieve(length): For a given length, iterate through all substrings of that length (using a fixed-size sliding window). For each window, calculate window_length - max_frequency_in_window. If this is ≤ k for any window, return true. This check takes O(n) time.
    • Binary Search: The range for the binary search would be [1, s.length].
    • Trade-offs: More complex than the optimal sliding window.
      • Time: O(n log n) because the binary search performs log n iterations, and each canAchieve call takes O(n).
      • Space: O(1) or O(26).
    • Why not optimal: While O(n log n) is often good, the direct sliding window approach achieves O(n) because it avoids redundant calculations by efficiently expanding and shrinking the window.

This is a fantastic solution and an equally impressive post-mortem. You've clearly mastered this pattern and its nuances. Keep up the excellent work and the rigorous TDD approach!

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

🤖 Gemini Code Review (Model: gemini-2.5-flash)

This is an excellent solution and a very well-documented post-mortem! It demonstrates a strong understanding of the problem, the chosen algorithmic pattern, and thorough self-reflection.

Let's dive into the review.


1. Pattern Identification

This solution correctly uses the Variable-Size Sliding Window pattern.

  • Why this pattern? We are looking for the longest substring that satisfies a certain condition. The condition (at most k replacements) can be checked and maintained efficiently as we expand and contract a window. The "variable-size" aspect comes from the while loop that shrinks the window only when the condition is violated, allowing the window to grow as large as possible.

2. Complexity Verification

The stated complexities in your POST_MORTEM.md are accurate.

  • Time Complexity: O(N)

    • The right pointer iterates through the string s once, from 0 to s.length - 1. This is N operations.
    • The left pointer also moves at most N times across the entire execution of the for loop. It only moves forward.
    • Inside the loops, operations like charCount[s[right]] = ..., Math.max(), and charCount[s[left]]-- are all constant time operations because charCount is a hash map (or dictionary) that stores at most 26 entries (for uppercase English letters).
    • Therefore, the total time complexity is linear, O(N).
  • Space Complexity: O(1)

    • The charCount object stores the frequencies of characters within the current window. Since the input string consists only of uppercase English letters, there are at most 26 distinct characters.
    • The size of charCount is bounded by the size of the alphabet, which is a constant (26). Thus, the space complexity is O(1).

3. Key Insight

The key insight, as correctly identified in your post-mortem, is the condition for a valid window:

window_length - max_frequency_in_window <= k

Let's break down why this is the "aha!" moment:

  1. Goal: We want to make a substring consist of only one repeating character.
  2. Strategy: To achieve this with the fewest replacements, we should identify the most frequent character already present in the substring and change all other characters to match it.
  3. Replacements Needed: The number of characters that are not the most frequent character in the current window is (window_length - max_frequency_in_window).
  4. Constraint: We are allowed at most k replacements.
  5. Conclusion: If the replacements_needed <= k, then the current window is valid.

The brilliance here is realizing that maxFreq doesn't need to be perfectly accurate for the current [left, right] window if left has moved. The maxFreq variable in your code tracks the highest frequency achieved by any single character within the largest valid window encountered so far. This is sufficient because we are looking for the longest possible window. If a window [left, right] becomes invalid, we shrink left. Even if maxFreq (which was set when right was further left) is for a character that is now outside the window or has a reduced count, it still serves as a valid upper bound for the actual maxFreq in the current (shrunk) window. If window_length - old_max_freq_from_larger_window <= k, it implies that the actual max_freq in the current (shrunk) window would lead to an even smaller or equal number of required replacements, thus also satisfying the condition. This allows us to avoid recomputing maxFreq when shrinking, which would be more complex (e.g., using a max-heap or re-scanning the frequency map).

4. Edge Case Analysis

Your longest-repeating-character-replacement.test.ts covers the critical edge cases well, and your POST_MORTEM.md confirms this.

  • k=0 (no replacements allowed): Correctly returns 1 for "ABCD", 4 for "AAAA".
  • k very large (can replace entire string): Correctly returns full string length for "ABCDE", k=10.
  • ✅ Single character string: Correctly returns 1 for "A", k=1.
  • ✅ All characters the same: Correctly returns string length.
  • ✅ Both example cases from the problem statement are covered.

The current solution handles all these cases correctly due to the robust sliding window logic.

5. Learning Points

  • Similar Problems using this pattern:

    1. LeetCode 3: Longest Substring Without Repeating Characters: A classic sliding window problem where the constraint is that all characters in the window must be unique.
    2. LeetCode 1004: Max Consecutive Ones III: This is almost identical to the current problem. Given a binary array and an integer k, return the maximum number of consecutive 1s if you can flip at most k 0s. Here, 0s are the "other characters" you can replace, and 1s are the "repeating character."
    3. LeetCode 340: Longest Substring with At Most K Distinct Characters (Premium): Find the longest substring that contains at most k distinct characters. The constraint shifts from "replacements" to "distinct characters."
    4. LeetCode 209: Minimum Size Subarray Sum: Another variable sliding window problem, but instead of finding the longest valid window, it seeks the minimum length subarray whose sum is at least a target value.
    5. LeetCode 76: Minimum Window Substring: A more complex variation where the window must contain all characters of a target string.
  • Common Mistakes with this pattern:

    • Off-by-one errors: Incorrectly calculating window size (right - left instead of right - left + 1).
    • Using if instead of while for shrinking: The while loop is crucial because multiple elements might need to be removed from the left to satisfy the window condition again.
    • Incorrectly updating maxFreq: As discussed in the "Key Insight," it's critical to understand when maxFreq needs to be precisely maintained for the current window versus when a historical maxFreq is sufficient (as in this problem).
    • Forgetting to update frequency map: Not incrementing charCount[s[right]] or decrementing charCount[s[left]].
    • Not handling empty string or single-character string: Though constraints usually prevent empty strings, it's good practice to consider them.
  • Variations of this problem:

    • Instead of finding the longest substring, find the count of all such valid substrings.
    • Allow replacements for a different character set (e.g., lowercase, numbers).
    • Find the longest substring where you can change at most k characters to make it an alternating sequence (e.g., "ABABAB").
    • What if k replacements are allowed per character type (e.g., you can change at most k 'A's to 'B's, k 'B's to 'C's, etc.)? This would significantly complicate the frequency tracking.

6. Code Quality

The code quality is excellent.

  • Variable Naming: charCount, maxFreq, maxLength, left, right are all standard, descriptive, and easy to understand.
  • Code Structure: The function is well-defined, the loop structure (outer for for right, inner while for left) is typical for sliding window and clear.
  • Readability: The comments clearly explain the purpose of the function and key steps. The logic is straightforward.
  • TypeScript Best Practices:
    • Uses export for the function.
    • Appropriate type annotations (s: string, k: number, Record<string, number>, number).
    • Uses const for charCount, maxLength, maxFreq and let for left and right where reassignment is needed.
    • The nullish coalescing operator (charCount[s[right]] || 0) + 1 is a clean way to handle initial character counts.

No significant improvements needed here; it's very solid.

7. Alternative Approaches

  1. Brute Force (O(N^3) or O(N^2)):

    • Approach: Iterate through all possible substrings s[i...j]. For each substring, count the frequency of each character. Determine the max_frequency within that substring. Check if (j - i + 1) - max_frequency <= k. If valid, update the maximum length.
    • Trade-offs: Simple to understand conceptually but highly inefficient. O(N^2) substrings, and for each, O(N) to count frequencies (or O(26) if using a map). Total O(N^3) or O(N^2 * 26). Clearly too slow for N = 10^5.
  2. Binary Search on the Answer (O(N log N * 26)):

    • Approach: The answer (the maximum length) can range from 1 to N. We can binary search on this possible length.
    • For a given mid_length (the length we are testing):
      • Iterate through all substrings of mid_length (i.e., s[0...mid_length-1], s[1...mid_length], etc.).
      • For each substring of mid_length, calculate character frequencies (O(26)).
      • Check if mid_length - max_frequency <= k for any character in that window. If yes, it's possible to achieve mid_length, so we try for a longer length (low = mid + 1). Otherwise, we need a shorter length (high = mid - 1).
    • Trade-offs: This approach is O(N * 26) for the check function (iterating through N starting points, each doing O(26) work), multiplied by log N for the binary search. Total O(N log N * 26). While better than brute force, it's still less optimal than the O(N) sliding window. It's a useful pattern for problems where a direct linear scan is not obvious, but here, the sliding window is more direct and efficient.

Overall, this is a fantastic solution, well-tested, and thoroughly analyzed. Keep up the great work!

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

Code Review in Progress

Review Checklist

  • Read all changed files
  • Verify algorithm correctness
  • Validate complexity analysis
  • Check test coverage
  • Identify patterns and key insights
  • Suggest related problems
  • Provide comprehensive review feedback

View job run

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

🤖 Gemini Code Review (Model: gemini-2.5-flash)

Here's a detailed review of your LeetCode solution for "Longest Repeating Character Replacement," structured to provide educational feedback.


1. Pattern Identification

This solution effectively uses the Variable-Size Sliding Window pattern. Specifically, it's a variant where the window expands as long as a certain condition (number of replacements needed) is met, and shrinks when that condition is violated. The goal is to find the maximum length of such a valid window.

This pattern is particularly suitable for problems that ask for the longest/shortest subarray/substring that satisfies a given property, often involving a constraint (k in this case).

2. Complexity Verification

  • Stated Time Complexity: O(n)

    • Verification: Accurate. The right pointer iterates through the string s once, from 0 to s.length - 1. The left pointer also moves monotonically forward and will iterate at most s.length times in total across all while loop executions. Since each character is processed by both left and right pointers at most a constant number of times, the overall time complexity is linear, O(n).
  • Stated Space Complexity: O(1)

    • Verification: Accurate. The charCount object (or frequency map) stores the counts of uppercase English letters. Since there are only 26 possible uppercase English letters, the size of this map is bounded by a constant (26). Therefore, the space complexity is O(1).

3. Key Insight

The core "aha!" moment for this problem lies in the condition used to determine if a substring within the sliding window is valid, and how maxFreq is handled.

  1. Validity Condition: A substring (window) s[left...right] is valid if we can make all its characters the same using at most k replacements. To achieve this, we should aim to change all characters that are not the most frequent character in the window to that most frequent character. The number of characters that need to be replaced is (current_window_length) - (max_frequency_of_any_character_in_window).

    • Thus, the window is valid if: (right - left + 1) - maxFreq <= k.
  2. maxFreq Handling (Subtlety): The maxFreq variable is updated with maxFreq = Math.max(maxFreq, charCount[s[right]]). Crucially, when the window shrinks (i.e., left moves and charCount[s[left]] is decremented), maxFreq is not necessarily decremented or recalculated. This might seem incorrect at first glance because maxFreq might no longer represent the true maximum frequency within the current, shrunken window s[left...right].

    • Why it works: We are looking for the longest possible valid substring. If maxFreq retains a value from a previous, potentially larger, window that was valid, it means we are using a "stricter" (or at least not looser) condition for the current window's validity. If a window [left, right] is valid with an maxFreq that's actually higher than the true maxFreq for [left, right], it means the window is even more valid than we think. This doesn't prevent us from finding the longest window because maxLength will always store the maximum right - left + 1 achieved by any valid window. The maxFreq being tracked is effectively the maximum frequency ever encountered in any window we've considered that could potentially form a part of the longest repeating substring. This ensures that maxLength accurately captures the largest possible window.

4. Edge Case Analysis

The POST_MORTEM.md and longest-repeating-character-replacement.test.ts demonstrate excellent coverage of edge cases.

  • k = 0 (No replacements allowed):
    • characterReplacement('ABCD', 0) returns 1. Correct, as each character forms a substring of length 1.
    • characterReplacement('AAAA', 0) returns 4. Correct, as no replacements are needed for an already repeating string.
  • k is very large (can replace entire string):
    • characterReplacement('ABCDE', 10) returns 5. Correct, as k=10 is more than enough to make the entire string AAAAA (or BBBBB, etc.).
  • Single character string:
    • characterReplacement('A', 1) returns 1. Correct.
  • String with all same characters:
    • characterReplacement('AAAA', 0) returns 4. Covered.
  • Minimum string length (1): Covered by single character string.
  • Maximum string length (10^5): The O(N) solution handles this efficiently.

The current solution handles all mentioned edge cases correctly.

5. Learning Points

  • Similar Problems using this pattern:

    • LeetCode 3. Longest Substring Without Repeating Characters: A classic fixed-size alphabet sliding window problem where the constraint is "no repeating characters."
    • LeetCode 1004. Max Consecutive Ones III: This problem is almost identical! Given a binary array and k flips, find the longest subarray of ones. Here, instead of maxFreq, you track the count of ones, and the condition becomes window_length - count_of_ones <= k (where k is flips allowed for zeros).
    • LeetCode 340. Longest Substring with At Most K Distinct Characters: Another variable-size sliding window. The constraint is the number of distinct characters in the window, rather than replacements.
    • LeetCode 209. Minimum Size Subarray Sum: This uses a variable-size sliding window, but the goal is to find the minimum length subarray that meets a sum target, rather than the maximum.
    • LeetCode 438. Find All Anagrams in a String: Fixed-size sliding window to find all occurrences of anagrams.
  • Common Mistakes with this pattern:

    • Off-by-one errors: Especially in calculating window length (right - left + 1) or pointer movements.
    • Incorrectly updating frequency maps: Forgetting to decrement counts when shrinking the window.
    • Mismanaging maxFreq: As discussed above, the subtle behavior of maxFreq not always strictly reflecting the current window's max frequency is a key point to understand. If one were to try to meticulously recalculate maxFreq on every left pointer movement, it would lead to a less efficient O(N*alphabet_size) solution.
    • Infinite loops: If the while condition for shrinking is not correctly formulated, the window might never shrink, leading to an infinite loop or incorrect results.
  • Variations of this problem:

    • Fixed-size window: Find the best substring of a specific length.
    • Count of valid substrings: Instead of the longest, count how many substrings satisfy the condition.
    • Minimum replacements: Given a target length, find the minimum k needed.
    • Different character sets: Problems involving different character sets (e.g., Unicode) would require a more generalized frequency map (e.g., a Map in JavaScript) rather than an array or Record<string, number> if the alphabet size is too large or not fixed.

6. Code Quality

The code quality is excellent:

  • Variable Naming: s, k, charCount, maxFreq, maxLength, left, right are all descriptive and follow common conventions for this pattern.
  • Code Structure and Readability: The function is well-defined, and the logic flows clearly from expanding the window, updating frequencies, checking the validity condition, shrinking if necessary, and updating the result. Comments are helpful but the code is largely self-documenting due to good naming.
  • TypeScript Best Practices:
    • Uses export for testability.
    • Record<string, number> is appropriate for the frequency map.
    • || 0 is a concise and standard way to initialize counts in JavaScript.
  • Comments: The JSDoc comment for the function is clear, and the inline comments explain the key steps.
  • TDD Approach: The POST_MORTEM.md indicates a TDD approach was used, which is highly commendable and likely contributed to the clean, bug-free implementation and comprehensive test suite.

7. Alternative Approaches

  1. Binary Search on the Answer:

    • Approach: We are looking for the maximum possible length of a repeating character substring. The possible lengths range from 1 to s.length. We can binary search on this range. For a given mid_length, we can create a helper function check(mid_length) that uses a sliding window (of fixed size mid_length) to iterate through s. For each window, it calculates the maxFreq and checks if mid_length - maxFreq <= k. If such a window exists, mid_length is a possible answer, and we try for a larger length; otherwise, we try for a smaller length.
    • Trade-offs:
      • Time Complexity: O(N log N) because the binary search takes log N iterations, and each check function takes O(N). This is less optimal than the current O(N) solution.
      • Space Complexity: O(1).
      • Pros: Can be an intuitive approach for problems asking for "maximum X such that Y is possible."
      • Cons: Slower than the direct sliding window.
  2. Brute Force (Nested Loops):

    • Approach: Iterate through all possible substrings (O(N^2) substrings). For each substring, count character frequencies and determine if length - maxFreq <= k. This would involve another loop or map operation (O(length) or O(alphabet_size)), leading to an O(N^3) or O(N^2 * alphabet_size) solution.
    • Trade-offs:
      • Time Complexity: O(N^3) or O(N^2 * alphabet_size). Highly inefficient for N = 10^5.
      • Space Complexity: O(1) or O(alphabet_size).
      • Pros: Simple to understand and implement for very small constraints.
      • Cons: Not suitable for typical LeetCode constraints.

Overall, this is an excellent solution with strong understanding and implementation of the sliding window pattern. The detailed post-mortem and comprehensive tests further highlight a robust problem-solving process. Keep up the great work!

@pertrai1 pertrai1 merged commit c04cb5e into main Feb 7, 2026
2 of 4 checks passed
@pertrai1 pertrai1 deleted the tdd/424-longest-repeating-character-replacement branch February 7, 2026 02:14
@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown

📅 Spaced Repetition Reviews Scheduled!

Great job solving #0424 - Longest Repeating Character Replacement! 🎉

To help you retain this knowledge long-term, I've scheduled 5 review sessions using spaced repetition:

Review Interval Schedule Logic
1st Review 1 day after solving Scheduled now
2nd Review 3 days after 1st review Auto-scheduled when 1st completes
3rd Review 7 days after 2nd review Auto-scheduled when 2nd completes
4th Review 14 days after 3rd review Auto-scheduled when 3rd completes
5th Review 30 days after 4th review Auto-scheduled when 4th completes

What to expect:

  • Your 1st review is scheduled for tomorrow
  • Each subsequent review is scheduled automatically when you complete the previous one
  • This ensures proper spacing even if you complete a review a few days late
  • GitHub issues will be created automatically for each review
  • Each issue will link back to your solution

🧠 Why Spaced Repetition?

Research shows that reviewing material at increasing intervals dramatically improves retention. This adaptive scheduling ensures optimal spacing based on when you actually complete each review!

Check docs/reviews/review-schedule.json to see your review schedule.

github-actions Bot pushed a commit that referenced this pull request Feb 7, 2026
…Replacement

  Problem: #0424 - Longest Repeating Character Replacement
  PR: #225
  First review scheduled (subsequent reviews auto-scheduled on completion)

  [skip ci]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code challenge this is a coding challenge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants