2981. Find Longest Special Substring That Occurs Thrice I
Problem Description
You need to find the length of the longest "special substring" that appears at least 3 times in a given string s.
A special substring is defined as a substring that consists of only a single repeated character. For example:
"aaa"is special (all 'a's)"bb"is special (all 'b's)"abc"is NOT special (different characters)
The task is to:
- Find all possible special substrings in the string
s - Identify which of these special substrings occur at least 3 times
- Return the length of the longest such substring
If no special substring appears at least 3 times, return -1.
Example walkthrough:
-
If
s = "aaaa", the special substrings are:"a"appears 4 times"aa"appears 3 times"aaa"appears 2 times"aaaa"appears 1 time
The longest special substring appearing at least 3 times is
"aa"with length 2.
Key points:
- The string
sconsists only of lowercase English letters - A substring must be contiguous (consecutive characters)
- The same special substring can overlap in different occurrences
- You're looking for the maximum length among all qualifying special substrings
Intuition
The key observation is that if a special substring of length x appears at least 3 times, then a special substring of length x-1 must also appear at least 3 times. This is because any substring of length x contains multiple substrings of length x-1.
For example, if "aaa" appears 3 times, then "aa" must appear at least 3 times (actually more, since each "aaa" contains two "aa" substrings).
This monotonic property creates a pattern like: true, true, true, ..., false, false as we increase the length. We want to find the last true position (maximum valid length). Using our standard binary search template that finds the first true, we can invert the condition: define feasible(x) as "length x does NOT work" (i.e., no special substring of length x appears at least 3 times). Then the pattern becomes false, false, ..., true, true and we find the first infeasible length, then subtract 1.
To efficiently count occurrences of special substrings of a given length, we scan through consecutive runs of the same character. When we encounter a run of length L, it contains max(0, L - x + 1) special substrings of length x. For example, "aaaaa" (length 5) contains 3 substrings of length 3: starting at positions 0, 1, and 2.
The algorithm combines these two ideas:
- Binary search using the template with
first_true_indexto find the first length that does NOT work - For each candidate length, scan through the string to find all runs of identical characters
- Count how many special substrings of that length each run contributes
- Check if any character has at least 3 special substrings of that length
Learn more about Binary Search and Sliding Window patterns.
Solution Implementation
1from collections import defaultdict
2
3class Solution:
4 def maximumLength(self, s: str) -> int:
5 """
6 Find the maximum length of a special substring that occurs at least 3 times.
7 A special substring consists of a single repeated character.
8 """
9 string_length = len(s)
10
11 def is_infeasible(target_length: int) -> bool:
12 """
13 Check if target_length is infeasible (no special substring of this
14 length appears at least 3 times).
15 Returns True if infeasible, False if feasible.
16 """
17 if target_length == 0:
18 return False # Length 0 is always feasible (edge case)
19
20 occurrence_count = defaultdict(int)
21
22 # Find consecutive character runs
23 i = 0
24 while i < string_length:
25 j = i + 1
26 while j < string_length and s[j] == s[i]:
27 j += 1
28
29 # Count substrings of target_length from this run
30 group_length = j - i
31 possible_substrings = max(0, group_length - target_length + 1)
32 occurrence_count[s[i]] += possible_substrings
33
34 i = j
35
36 # Return True if infeasible (no character has 3+ occurrences)
37 return max(occurrence_count.values()) < 3
38
39 # Binary search using the template: find first infeasible length
40 left, right = 1, string_length
41 first_true_index = -1
42
43 while left <= right:
44 mid = (left + right) // 2
45 if is_infeasible(mid):
46 first_true_index = mid
47 right = mid - 1
48 else:
49 left = mid + 1
50
51 # Handle edge cases
52 if first_true_index == -1:
53 # All lengths are feasible (shouldn't happen with proper bounds)
54 return string_length
55 if first_true_index == 1:
56 # Even length 1 is infeasible
57 return -1
58
59 # Return the last feasible length
60 return first_true_index - 1
611class Solution {
2 private String inputString;
3 private int stringLength;
4
5 /**
6 * Finds the maximum length of a special substring that appears at least 3 times.
7 * A special substring consists of only a single repeated character.
8 */
9 public int maximumLength(String s) {
10 this.inputString = s;
11 this.stringLength = s.length();
12
13 // Binary search using template: find first infeasible length
14 int left = 1;
15 int right = stringLength;
16 int firstTrueIndex = -1;
17
18 while (left <= right) {
19 int mid = left + (right - left) / 2;
20
21 if (isInfeasible(mid)) {
22 firstTrueIndex = mid;
23 right = mid - 1;
24 } else {
25 left = mid + 1;
26 }
27 }
28
29 // Handle edge cases
30 if (firstTrueIndex == -1) {
31 return stringLength; // All lengths feasible
32 }
33 if (firstTrueIndex == 1) {
34 return -1; // Even length 1 infeasible
35 }
36
37 return firstTrueIndex - 1;
38 }
39
40 /**
41 * Checks if the given length is infeasible (no special substring of this
42 * length appears at least 3 times).
43 * Returns true if infeasible, false if feasible.
44 */
45 private boolean isInfeasible(int targetLength) {
46 int[] occurrenceCount = new int[26];
47
48 for (int i = 0; i < stringLength;) {
49 int j = i + 1;
50
51 while (j < stringLength && inputString.charAt(j) == inputString.charAt(i)) {
52 j++;
53 }
54
55 int charIndex = inputString.charAt(i) - 'a';
56 occurrenceCount[charIndex] += Math.max(0, j - i - targetLength + 1);
57
58 if (occurrenceCount[charIndex] >= 3) {
59 return false; // Feasible - found 3+ occurrences
60 }
61
62 i = j;
63 }
64
65 return true; // Infeasible - no character has 3+ occurrences
66 }
67}
681class Solution {
2public:
3 int maximumLength(string s) {
4 int stringLength = s.size();
5
6 // Lambda: returns true if length is infeasible (no 3+ occurrences)
7 auto isInfeasible = [&](int targetLength) {
8 int occurrenceCount[26]{};
9
10 for (int i = 0; i < stringLength;) {
11 int j = i + 1;
12 while (j < stringLength && s[j] == s[i]) {
13 ++j;
14 }
15
16 int charIndex = s[i] - 'a';
17 int segmentLength = j - i;
18 occurrenceCount[charIndex] += max(0, segmentLength - targetLength + 1);
19
20 if (occurrenceCount[charIndex] >= 3) {
21 return false; // Feasible
22 }
23
24 i = j;
25 }
26 return true; // Infeasible
27 };
28
29 // Binary search using template: find first infeasible length
30 int left = 1, right = stringLength;
31 int firstTrueIndex = -1;
32
33 while (left <= right) {
34 int mid = left + (right - left) / 2;
35
36 if (isInfeasible(mid)) {
37 firstTrueIndex = mid;
38 right = mid - 1;
39 } else {
40 left = mid + 1;
41 }
42 }
43
44 // Handle edge cases
45 if (firstTrueIndex == -1) {
46 return stringLength;
47 }
48 if (firstTrueIndex == 1) {
49 return -1;
50 }
51
52 return firstTrueIndex - 1;
53 }
54};
551/**
2 * Finds the maximum length of a substring that appears at least 3 times in the string
3 * where the substring consists of only one unique character
4 */
5function maximumLength(s: string): number {
6 const stringLength: number = s.length;
7
8 /**
9 * Returns true if targetLength is infeasible (no 3+ occurrences)
10 */
11 const isInfeasible = (targetLength: number): boolean => {
12 const characterCounts: number[] = Array(26).fill(0);
13
14 let currentIndex: number = 0;
15 while (currentIndex < stringLength) {
16 let nextIndex: number = currentIndex + 1;
17 while (nextIndex < stringLength && s[nextIndex] === s[currentIndex]) {
18 nextIndex++;
19 }
20
21 const characterIndex: number = s[currentIndex].charCodeAt(0) - 'a'.charCodeAt(0);
22 const segmentLength: number = nextIndex - currentIndex;
23 const possibleSubstrings: number = Math.max(0, segmentLength - targetLength + 1);
24 characterCounts[characterIndex] += possibleSubstrings;
25
26 if (characterCounts[characterIndex] >= 3) {
27 return false; // Feasible
28 }
29
30 currentIndex = nextIndex;
31 }
32
33 return true; // Infeasible
34 };
35
36 // Binary search using template: find first infeasible length
37 let left: number = 1;
38 let right: number = stringLength;
39 let firstTrueIndex: number = -1;
40
41 while (left <= right) {
42 const mid: number = Math.floor((left + right) / 2);
43
44 if (isInfeasible(mid)) {
45 firstTrueIndex = mid;
46 right = mid - 1;
47 } else {
48 left = mid + 1;
49 }
50 }
51
52 // Handle edge cases
53 if (firstTrueIndex === -1) {
54 return stringLength;
55 }
56 if (firstTrueIndex === 1) {
57 return -1;
58 }
59
60 return firstTrueIndex - 1;
61}
62Solution Approach
The solution implements a binary search combined with a sliding window counting technique.
Binary Search Template Setup
We use the standard binary search template to find the first length that does NOT work:
left, right = 1, n first_true_index = -1 while left <= right: mid = (left + right) // 2 if not feasible(mid): # length mid does NOT have 3+ occurrences first_true_index = mid right = mid - 1 else: left = mid + 1 # first_true_index is the first infeasible length # Answer is first_true_index - 1 (last feasible length)
The feasible(x) function returns True if length x is infeasible (no special substring of length x appears at least 3 times). This inverts the natural condition so we can use the standard "find first true" template.
Feasible Function Implementation
The feasible check determines if there exists a special substring of length x appearing at least 3 times:
-
Initialize a counter: Use
defaultdict(int)to count occurrences for each character. -
Traverse the string with two pointers to find consecutive runs:
i = 0 while i < n: j = i + 1 while j < n and s[j] == s[i]: j += 1 # Process run from i to j-1 i = j -
Count special substrings in each run:
- For a run of length
L = j - i - Number of special substrings of length
x=max(0, L - x + 1) - Add this count to
cnt[s[i]]
- For a run of length
-
Return the inverse for template compatibility:
return max(cnt.values()) < 3 # True if infeasible
Final Result
After binary search completes:
- If
first_true_index == 1, even length 1 doesn't work, return-1 - If
first_true_index == -1, all lengths work (shouldn't happen with proper bounds) - Otherwise, return
first_true_index - 1as the maximum valid length
The time complexity is O(n log n) where each binary search iteration takes O(n) time to scan the string.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "aaabaaabcaaab".
Step 1: Binary Search Setup
- String length n = 13
- Binary search range:
left = 1,right = 13 first_true_index = -1(tracks first infeasible length)
Step 2: Binary Search Iterations
First iteration: mid = (1 + 13) / 2 = 7
- Check if length 7 is infeasible (no special substring appears ≥ 3 times)
- Scanning the string for runs:
"aaa"(length 3): contributesmax(0, 3-7+1) = 0substrings- All runs have length ≤ 3, so no length-7 special substrings exist
- No character has 3+ occurrences → infeasible =
True - Update:
first_true_index = 7,right = 6
Second iteration: mid = (1 + 6) / 2 = 3
- Check if length 3 is infeasible
- Scanning for runs:
"aaa"at position 0: contributes3-3+1 = 1substring"aaa""aaa"at position 4: contributes1substring"aaa""aaa"at position 9: contributes1substring"aaa"
- Count:
'a' → 3 occurrencesof length-3 special substrings - Length 3 works! → infeasible =
False - Update:
left = 4
Third iteration: mid = (4 + 6) / 2 = 5
- Check if length 5 is infeasible
- All runs have length ≤ 3, so no length-5 special substrings exist
- infeasible =
True - Update:
first_true_index = 5,right = 4
Fourth iteration: mid = (4 + 4) / 2 = 4
- Check if length 4 is infeasible
- All runs have length ≤ 3, so no length-4 special substrings exist
- infeasible =
True - Update:
first_true_index = 4,right = 3
Step 3: Binary Search Termination
left = 4 > right = 3→ loop endsfirst_true_index = 4(first infeasible length)- Return
first_true_index - 1 = 3
The answer is 3 because "aaa" appears exactly 3 times in the string, making it the longest special substring that meets the requirement.
Time and Space Complexity
Time Complexity: O(n × log n)
The algorithm uses binary search on the answer space from 0 to n, which takes O(log n) iterations. In each iteration, the check function is called.
The check function iterates through the string once with two pointers (i and j), taking O(n) time to identify consecutive segments of identical characters. For each segment, it performs constant-time operations to update the count in the dictionary. The max(cnt.values()) operation at the end takes O(|Σ|) time where |Σ| is the size of the character set (26 for lowercase English letters).
Since |Σ| = 26 is a constant and n dominates, the time complexity per check call is O(n). Therefore, the overall time complexity is O(n × log n).
Space Complexity: O(|Σ|) or O(1)
The space is primarily used by the cnt dictionary in the check function, which stores at most |Σ| entries (one for each distinct character in the string). Since |Σ| = 26 for lowercase English letters, this is effectively O(1) constant space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template
Pitfall: Not using the standard binary search template with first_true_index tracking.
Common mistakes:
# WRONG: Using while left < right with different update rules while left < right: mid = (left + right + 1) >> 1 if check(mid): left = mid else: right = mid - 1
Correct approach using template:
# CORRECT: Standard template with first_true_index left, right = 1, n first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): # Define feasible as "condition becomes TRUE" first_true_index = mid right = mid - 1 else: left = mid + 1
2. Incorrect Counting of Overlapping Substrings
Pitfall: Incorrectly counting how many special substrings of length x can be formed from a consecutive run.
Incorrect approach:
# WRONG: Dividing the group length by target length possible_substrings = group_length // target_length
Why it's wrong: For a run like "aaaa" (length 4) and target length 2:
- Wrong calculation:
4 // 2 = 2 - Correct count: 3 overlapping substrings at positions [0:2], [1:3], [2:4]
Correct approach:
# CORRECT: Using sliding window formula
possible_substrings = max(0, group_length - target_length + 1)
3. Forgetting to Invert the Condition for "Find Maximum"
Pitfall: The template finds the first true position, but we need the last feasible position (maximum length). Must invert the condition.
Wrong:
# WRONG: feasible returns True when length works
def feasible(x):
return max(counts.values()) >= 3 # True = works
Correct:
# CORRECT: feasible returns True when length does NOT work (infeasible)
def is_infeasible(x):
return max(counts.values()) < 3 # True = doesn't work
# Then: first_true_index - 1 = last feasible length
4. Forgetting Edge Cases
Pitfall: Not handling when first_true_index == 1 (even length 1 is infeasible).
Wrong:
return first_true_index - 1 # Returns 0 when should return -1
Correct:
if first_true_index == 1: return -1 # No valid length exists return first_true_index - 1
How does merge sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!