Learnitweb

Problem 395: Longest Substring with At Least K Repeating Characters

Understanding the Problem

You are given a string s and an integer k.
You must find the length of the longest substring where every character appears at least k times.

For example:

Input: s = “aaabb”, k = 3
Output: 3
Valid substring: “aaa” (each character frequency ≥ 3)

Another example:

Input: s = “ababbc”, k = 2
Output: 5
Valid substring: “ababb” (both ‘a’ and ‘b’ appear at least 2 times)


Approach (Explained in Simple Language)

This is a divide-and-conquer string problem.
The key observation is:

If a character appears fewer than k times in the whole string,
that character can never be part of a valid answer.

So, that character acts like a boundary that splits the string into smaller parts.

For example:
String = “ababbc”, k = 2
Character ‘c’ appears only once.

So ‘c’ cannot be included.
We split:
“ababb” + ” ” + “”
Now solve separately on “ababb”.


Step-by-step Plan

1. Count frequency of every character in the current substring

If all characters appear at least k times, the whole substring is valid.

2. Find characters that appear less than k times

These characters “break” the string into smaller substrings because they cannot be part of the valid answer.

3. Split the string using these characters and recursively solve each part

Take the maximum answer from all parts.

4. Base Case

If the substring length is less than k, it can never qualify.

This divide-and-conquer approach gives excellent efficiency.


Java Code

public class LongestSubstringAtLeastKRepeating {

    public int longestSubstring(String s, int k) {
        return helper(s, 0, s.length(), k);
    }

    private int helper(String s, int start, int end, int k) {
        if (end - start < k) {
            return 0;
        }

        int[] freq = new int[26];
        for (int i = start; i < end; i++) {
            freq[s.charAt(i) - 'a']++;
        }

        for (int i = start; i < end; i++) {
            if (freq[s.charAt(i) - 'a'] < k) {
                int left = helper(s, start, i, k);
                int right = helper(s, i + 1, end, k);
                return Math.max(left, right);
            }
        }

        return end - start;
    }

    public static void main(String[] args) {
        LongestSubstringAtLeastKRepeating solution = new LongestSubstringAtLeastKRepeating();
        System.out.println(solution.longestSubstring("ababbc", 2));
    }
}

Dry Run

Performing a dry run on:

s = “ababbc”
k = 2

Step 1: initial call

helper(“ababbc”, 0, 6, 2)
Substring = “ababbc”

Count frequencies:
a = 2
b = 3
c = 1

Since c has frequency < 2, it cannot be in the valid substring.

We find c at index 5.
Split:

Left substring: “ababb” (index 0 to 4)
Right substring: “” (index 6 to 5 → empty)

Now solve each.


Step 2: Solve left substring

helper(“ababbc”, 0, 5, 2)
Substring = “ababb”

Frequencies:
a = 2
b = 3

All characters have frequency ≥ 2.

So the entire substring is valid.

Return length = 5 – 0 = 5.


Step 3: Solve right substring

helper(“”, 6, 6, 2)
Length is 0 < k, so return 0.


Step 4: Combine

Left result = 5
Right result = 0
Answer = max(5, 0) = 5

Final Output = 5


Why This Works Efficiently

The algorithm avoids scanning all substrings.
Instead, it splits only when it finds a weak character (frequency < k).
This drastically reduces unnecessary checks, making the solution fast even for long strings.

Time complexity is approximately O(26 × n) in practice because each split reduces the search area.