↓Skip to main content
  1. LeetCode/

LeetCode 395: Longest Substring with At Least K Repeating Characters

·3 mins· ·
LeetCode Medium Sliding-Window Divide-and-Conquer Hash-Table String
Wei Yi Chung
Author
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
Table of Contents

Meta Data
#

Difficulty: medium First Attempt: 2026-01-17

  • Submissions: WA → RE ×2 → WA ×2 → AC (divide and conquer) → AC ×2 (sliding window)

Approach
#

“Every character in the window at least k times” is not monotonic: growing the window can make it invalid (a character appears once) or valid (a character reaches k). So “shrink the left when invalid” does not apply directly.

Divide and conquer
#

If a character appears fewer than k times in the whole string, the answer cannot contain it. Split the string on it and recurse on each part. When every character reaches k, the whole string is the answer.

Sliding window with a fixed number of distinct characters
#

Add a constraint that restores monotonicity: the window has exactly t distinct characters. Shrinking when there are more than t is monotonic. Run once for each t = 1..26, and update the answer when the window has t distinct characters that all reach k.

My Attempts
#

The first few versions slid directly, using not_enough to count occurrences of characters below k, and jumped the left end past any character whose total count was below k.

VersionProblem
Attempt 1, WAAfter the jump, the counts were not reset, so characters outside the window stayed counted. For s = "cbb", k = 2, the answer is 2 ("bb"); it returned 0
Two REsSwitched to running forward and backward; once a colon was missing, then the helper forgot to return
Attempt 4, WAAdded the return, but counts still were not reset. For s = "caab", k = 2, the answer is 2; it returned 0
Attempt 5, WAReset the counts, but the method itself is wrong: the condition is not monotonic, so the left end cannot tell whether to shrink. For s = "bcccbab", k = 3, the answer is 3 ("ccc"); it returned 0

Divide and conquer then passed, and I wrote the fixed-distinct-count window afterwards.

Solution
#

Divide and conquer:

class Solution:
    def longestSubstring(self, s: str, k: int) -> int:
        for c, cnt in Counter(s).items():
            if cnt < k:                            # the answer cannot contain c
                return max(self.longestSubstring(part, k) for part in s.split(c))
        return len(s)

Sliding window with a fixed number of distinct characters:

class Solution:
    def longestSubstring(self, s: str, k: int) -> int:
        best = 0
        for target in range(1, len(set(s)) + 1):   # exactly `target` distinct characters
            cnt = Counter()
            left = unique = enough = 0             # enough: distinct characters at k or more
            for right, c in enumerate(s):
                if cnt[c] == 0:
                    unique += 1
                cnt[c] += 1
                if cnt[c] == k:
                    enough += 1
                while unique > target:
                    d = s[left]
                    if cnt[d] == k:
                        enough -= 1
                    cnt[d] -= 1
                    if cnt[d] == 0:
                        unique -= 1
                    left += 1
                if unique == target and enough == target:
                    best = max(best, right - left + 1)
        return best

Complexity
#

  • Divide and conquer: each level removes at least one character, so at most 26 levels of O(n): O(26n).
  • Sliding window: O(26n), space O(26).

Mistakes to Avoid
#

  • Sliding directly when the condition is not monotonic.
  • Not resetting the counts when jumping the left end.

Interview Explanation
#

“Every character at least k times” is not monotonic, so a direct slide fails. One way is divide and conquer: a character with fewer than k occurrences cannot be in the answer, so split on it and recurse. Another is to add a constraint, enumerating the exact number of distinct characters in the window, which makes shrinking monotonic, then checking whether all of them reach k. Both are O(26n).

Related

LeetCode 1763: Longest Nice Substring
·2 mins
LeetCode Easy Sliding-Window Divide-and-Conquer Hash-Table String Bit-Manipulation
LeetCode 1763 note: a character whose other case never appears cannot be in the answer, so split on it and recurse, preferring the left side on ties
LeetCode 2516: Take K of Each Character From Left and Right
·2 mins
LeetCode Medium Sliding-Window Hash-Table String
LeetCode 2516 note: taking from both ends leaves a middle block in which each letter may appear at most total − k times; find the longest such window
LeetCode 1016: Binary String With Substrings Representing 1 To N
·3 mins
LeetCode Medium Sliding-Window String Bit-Manipulation Hash-Table
LeetCode 1016 note: s has at most 1000 characters, so there are few substrings of any fixed length and a large n must be False; checking from n downward fails fast, or extend up to 30 bits from each start to collect every value <= n
LeetCode 3694: Distinct Points Reachable After Substring Removal
·2 mins
LeetCode Medium Sliding-Window Prefix-Sum Hash-Table String
LeetCode 3694 note: the endpoint is the total displacement minus the removed segment’s displacement; a length-k window tracks that segment and each endpoint goes into a set
LeetCode 187: Repeated DNA Sequences
·1 min
LeetCode Medium Hash-Table String Bit-Manipulation Sliding-Window
LeetCode 187 note: count every length-10 substring in a hash map and return those seen more than once
LeetCode 424: Longest Repeating Character Replacement
·2 mins
LeetCode Medium Hash-Table String Sliding-Window
LeetCode 424 note: a variable sliding window where window length minus the top character count is the number of replacements; shrink when it exceeds k