Question

Formatted question description: https://leetcode.ca/all/395.html

Given a string s and an integer k, return the length of the longest substring of s such that the frequency of each character in this substring is greater than or equal to k.

Example 1:

Input: s = "aaabb", k = 3
Output: 3
Explanation: The longest substring is "aaa", as 'a' is repeated 3 times.


Example 2:

Input: s = "ababbc", k = 2
Output: 5
Explanation: The longest substring is "ababb", as 'a' is repeated 2 times and 'b' is repeated 3 times.


Constraints:

• 1 <= s.length <= 104
• s consists of only lowercase English letters.
• 1 <= k <= 105

Algorithm

The idea is divide and conquer.

To find the largest substring of s[i,j], first count the frequency, then traverse the frequency, find the first character whose frequency is less than k and greater than 0, and then find the position of this character,

The next analysis is very important, this character must not appear in any substring,

Because i and j are the entire substring, the frequency in ij does not reach k, so in any substring of ij, this character cannot reach the frequency k.

So there can be no such character, then divide and conquer at this position and return the maximum value of the first half and the second half.

Code

• public class Longest_Substring_with_At_Least_K_Repeating_Characters {

class Solution {

public int longestSubstring(String str, int k) {

if (str == null || str.length() == 0) {
return 0;
}

char[] s = str.toCharArray();
int result = 0;
int n = str.length();

// The maximum number of different letters is only 26. The number of different letters in the substring that meets the meaning of the question must be in the range of [1, 26]
for (int targetUniqueCount = 1; targetUniqueCount <= 26; targetUniqueCount++) {

int left = 0;
int right = 0;
int uniqueCnt = 0; // uniqueCnt to record the number of different letters in the substring
int[] countMap = new int[26];

while (right < n) {
if (countMap[s[right++] - 'a']++ == 0) ++uniqueCnt;
while (uniqueCnt > targetUniqueCount) {
if (--countMap[s[left++] - 'a'] == 0) --uniqueCnt;
}

boolean isValid = true;
for (int j = 0; j < 26; ++j) {
if (countMap[j] > 0 && countMap[j] < k) isValid = false;
}
if (isValid) result = Math.max(result, right - left);
}
}

return result;
}
}

class Solution_dfs_divide_conquer {
public int longestSubstring(String s, int k) {

if (s == null || s.length() == 0) {
return 0;
}

return dfs(s, k, 0, s.length() - 1);
}

private int dfs(String s, int k, int start, int end) {
if (start > end) { // @note: if ==, will cover in return clause
return 0;
}

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

for (int i = 0; i < 26; i++) {
if (count[i] > 0 && count[i] < k) { // @note: This character must not appear in any substring

// indexOf(int ch, int fromIndex)
int pos = s.indexOf((char) (i + 'a'), start); // @note: index after start-index

return Math.max(
dfs(s, k, start, pos - 1),
dfs(s, k, pos + 1, end)
);
}
}

return end - start + 1; // eg. aaabbbccc => not entering if of for loop
}
}
}

############

class Solution {
private String s;
private int k;

public int longestSubstring(String s, int k) {
this.s = s;
this.k = k;
return dfs(0, s.length() - 1);
}

private int dfs(int l, int r) {
int[] cnt = new int[26];
for (int i = l; i <= r; ++i) {
++cnt[s.charAt(i) - 'a'];
}
char split = 0;
for (int i = 0; i < 26; ++i) {
if (cnt[i] > 0 && cnt[i] < k) {
split = (char) (i + 'a');
break;
}
}
if (split == 0) {
return r - l + 1;
}
int i = l;
int ans = 0;
while (i <= r) {
while (i <= r && s.charAt(i) == split) {
++i;
}
if (i > r) {
break;
}
int j = i;
while (j <= r && s.charAt(j) != split) {
++j;
}
int t = dfs(i, j - 1);
ans = Math.max(ans, t);
i = j;
}
return ans;
}
}

• // OJ: https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/
// Time: O(N)
// Space: O(N)
// Ref: https://discuss.leetcode.com/topic/57134/two-short-c-solutions-3ms-and-6ms
class Solution {
private:
int longestSubstring(string &s, int k, int begin, int end) {
int cnt[26] = {};
for (int i = begin; i < end; ++i) cnt[s[i] - 'a']++;
int i = begin, ans = 0;
while (i < end) {
while (i < end && cnt[s[i] - 'a'] < k) ++i;
int j = i;
while (j < end && cnt[s[j] - 'a'] >= k) ++j;
if (i == begin && j == end) return end - begin;
ans = max(ans, longestSubstring(s, k, i, j));
i = j;
}
return ans;
}
public:
int longestSubstring(string s, int k) {
return longestSubstring(s, k, 0, s.size());
}
};

• '''
>>> s = "ababbc"
>>> set(s)
{'b', 'a', 'c'}

>>> s.count('a')
2
>>> s.count('b')
3

>>> s.split('a')
['', 'b', 'bbc']
'''

class Solution:
def longestSubstring(self, s: str, k: int) -> int:
for c in set(s):
# no need to try all chars with count<k, get the 1st one and return
if s.count(c) < k: # @note: This character must not appear in any substring
return max([self.longestSubstring(t, k) for t in s.split(c)])
return len(s)

class Solution: # iterative, OJ passed
def longestSubstring(self, s: str, k: int) -> int:
n = len(s)
result = 0

# Iterate over the possible values of unique characters in the substring
for num_unique in range(1, 27):
freq_map = [0] * 26
left = 0
num_chars = 0
num_chars_at_least_k = 0

# Sliding window approach to find the longest substring with num_unique characters
for right in range(n):
if freq_map[ord(s[right]) - ord('a')] == 0:
num_chars += 1
freq_map[ord(s[right]) - ord('a')] += 1
if freq_map[ord(s[right]) - ord('a')] == k:
num_chars_at_least_k += 1

# Shrink the window if the number of unique characters exceeds num_unique
while num_chars > num_unique:
freq_map[ord(s[left]) - ord('a')] -= 1
if freq_map[ord(s[left]) - ord('a')] == k - 1:
num_chars_at_least_k -= 1
if freq_map[ord(s[left]) - ord('a')] == 0:
num_chars -= 1
left += 1

# Check if all characters in the substring occur at least k times
if num_chars == num_chars_at_least_k:
result = max(result, right - left + 1)

return result

############

class Solution:
def longestSubstring(self, s: str, k: int) -> int:
def dfs(l, r):
cnt = Counter(s[l : r + 1])
split = next((c for c, v in cnt.items() if v < k), '')
if not split:
return r - l + 1
i = l
ans = 0
while i <= r:
while i <= r and s[i] == split:
i += 1
if i >= r:
break
j = i
while j <= r and s[j] != split:
j += 1
t = dfs(i, j - 1)
ans = max(ans, t)
i = j
return ans

return dfs(0, len(s) - 1)


• func longestSubstring(s string, k int) int {
var dfs func(l, r int) int
dfs = func(l, r int) int {
cnt := [26]int{}
for i := l; i <= r; i++ {
cnt[s[i]-'a']++
}
var split byte
for i, v := range cnt {
if v > 0 && v < k {
split = byte(i + 'a')
break
}
}
if split == 0 {
return r - l + 1
}
i := l
ans := 0
for i <= r {
for i <= r && s[i] == split {
i++
}
if i > r {
break
}
j := i
for j <= r && s[j] != split {
j++
}
t := dfs(i, j-1)
ans = max(ans, t)
i = j
}
return ans
}
return dfs(0, len(s)-1)
}

func max(a, b int) int {
if a > b {
return a
}
return b
}