class Solution:
def longestSubstring(self, s: str, k: int) -> int:
def helper(l, r):
if r - l < k:
return 0
count = {}
for i in range(l, r):
count[s[i]] = count.get(s[i], 0) + 1
for mid in range(l, r):
if count[s[mid]] < k:
midNext = mid + 1
while midNext < r and count[s[midNext]] < k:
midNext += 1
return max(helper(l, mid), helper(midNext, r))
return r - l
return helper(0, len(s))
class Solution {
public:
int longestSubstring(string s, int k) {
return helper(s, 0, s.size(), k);
}
int helper(const string& s, int l, int r, int k) {
if (r - l < k) return 0;
int count[26] = {0};
for (int i = l; i < r; ++i) count[s[i] - 'a']++;
for (int mid = l; mid < r; ++mid) {
if (count[s[mid] - 'a'] < k) {
int midNext = mid + 1;
while (midNext < r && count[s[midNext] - 'a'] < k) midNext++;
return max(helper(s, l, mid, k), helper(s, midNext, r, k));
}
}
return r - l;
}
};
class Solution {
public int longestSubstring(String s, int k) {
return helper(s, 0, s.length(), k);
}
private int helper(String s, int l, int r, int k) {
if (r - l < k) return 0;
int[] count = new int[26];
for (int i = l; i < r; ++i) count[s.charAt(i) - 'a']++;
for (int mid = l; mid < r; ++mid) {
if (count[s.charAt(mid) - 'a'] < k) {
int midNext = mid + 1;
while (midNext < r && count[s.charAt(midNext) - 'a'] < k) midNext++;
return Math.max(helper(s, l, mid, k), helper(s, midNext, r, k));
}
}
return r - l;
}
}
var longestSubstring = function(s, k) {
function helper(l, r) {
if (r - l < k) return 0;
let count = {};
for (let i = l; i < r; ++i) {
count[s[i]] = (count[s[i]] || 0) + 1;
}
for (let mid = l; mid < r; ++mid) {
if (count[s[mid]] < k) {
let midNext = mid + 1;
while (midNext < r && count[s[midNext]] < k) midNext++;
return Math.max(helper(l, mid), helper(midNext, r));
}
}
return r - l;
}
return helper(0, s.length);
};
Given a string s
and an integer k
, find the length of the longest substring of s
such that every character in this substring appears at least k
times.
The substring must be contiguous, and you may not rearrange the string. There is no guarantee that a valid substring exists.
Key constraints:
k
times.s
.s = "aaabb"
and k = 3
, the answer is 3
because "aaa" is the longest substring in which every character appears at least 3 times.
The problem asks us to find the longest contiguous substring where each character appears at least k
times.
The brute-force way would be to check every possible substring, count the frequency of each character, and see if all frequencies are at least k
. However, this would be very slow for large strings since there are O(n2) substrings and counting frequencies for each takes O(n) time, resulting in O(n3) time complexity.
We want to optimize. Notice that if a character in the substring appears less than k
times, then any substring containing that character cannot be valid. So, if we split the string at such characters, we can recursively solve the problem on the resulting segments.
This is a classic "divide and conquer" or "recursive partitioning" approach, where we use the characters with insufficient frequency as natural split points.
The optimized solution uses recursion and frequency counting. Here's how it works step-by-step:
k
, it's impossible for every character to meet the requirement, so return 0.
k
, this character cannot be part of any valid substring.
k
times, then the entire substring is valid, so return its length.
We use a hash map or array to count character frequencies efficiently. This approach ensures that we only examine necessary substrings and don't waste time on obviously invalid ones. The recursion naturally breaks the problem into smaller parts, and we only process each segment as needed.
Let's walk through the example s = "ababbc"
, k = 2
:
k
. So, any valid substring cannot include 'c'.
Brute-force approach:
The optimized approach is efficient for practical input sizes and leverages the limited alphabet size.
To solve the "Longest Substring with At Least K Repeating Characters" problem, we move beyond brute force by recognizing that characters with insufficient frequency are natural split points. By recursively partitioning the string and checking only necessary substrings, we efficiently find the answer. The key insight is that any substring containing a "bad" character can't be valid, so we can safely ignore such segments. This divide-and-conquer approach is both elegant and efficient, especially for strings with a small alphabet.