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
s
consists 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 suggests we can use binary search on the answer. If we can find special substrings of length mid
appearing at least 3 times, we should search for longer ones. If not, we need to search for shorter ones.
To efficiently count occurrences of special substrings of a given length, we can use a sliding window approach. When we encounter a consecutive run of the same character (like "aaaaa"
), we can calculate how many special substrings of length x
it contains:
- A run of length
L
containsmax(0, L - x + 1)
special substrings of lengthx
- 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 on the length of the special substring (from 0 to
n
) - 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
This approach efficiently narrows down to the maximum valid length without having to explicitly enumerate all possible substrings.
Learn more about Binary Search and Sliding Window patterns.
Solution Approach
The solution implements a binary search combined with a sliding window counting technique.
Binary Search Setup
We set up binary search boundaries:
l = 0
: minimum possible length (represents no valid substring)r = n
: maximum possible length (entire string)
We search for the maximum length where a special substring appears at least 3 times using the pattern:
while l < r: mid = (l + r + 1) >> 1 # equivalent to (l + r + 1) // 2 if check(mid): l = mid # valid length found, search for longer else: r = mid - 1 # invalid, search for shorter
Check Function Implementation
The check(x)
function determines if there exists a special substring of length x
appearing at least 3 times:
-
Initialize a counter: Use
defaultdict(int)
to count occurrences of special substrings for each character. -
Traverse the string with two pointers:
i = 0 while i < n: j = i + 1 while j < n and s[j] == s[i]: j += 1
This finds consecutive runs of the same character
s[i]
. -
Count special substrings in each run:
- For a run from position
i
toj-1
(length =j - i
) - Number of special substrings of length
x
=max(0, j - i - x + 1)
- Add this count to
cnt[s[i]]
- For a run from position
-
Check if any character has enough occurrences:
return max(cnt.values()) >= 3
Example Walkthrough
For string "aaabbbccc"
checking length x = 2
:
- First run:
"aaa"
(length 3) → contributes3 - 2 + 1 = 2
substrings of"aa"
- Second run:
"bbb"
(length 3) → contributes3 - 2 + 1 = 2
substrings of"bb"
- Third run:
"ccc"
(length 3) → contributes3 - 2 + 1 = 2
substrings of"cc"
- None appear 3 times, so
check(2)
returnsfalse
Final Result
After binary search completes:
- If
l == 0
, no valid special substring exists, return-1
- Otherwise, return
l
as the maximum 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:
l = 0
,r = 13
Step 2: Binary Search Iterations
First iteration: mid = (0 + 13 + 1) / 2 = 7
- Check if any special substring of length 7 appears ≥ 3 times
- Scanning the string for runs:
"aaa"
(length 3): contributesmax(0, 3-7+1) = 0
substrings"b"
(length 1): contributes0
substrings"aaa"
(length 3): contributes0
substrings"b"
(length 1): contributes0
substrings"c"
(length 1): contributes0
substrings"aaa"
(length 3): contributes0
substrings"b"
(length 1): contributes0
substrings
- No character has 3+ occurrences →
check(7) = false
- Update:
r = 6
Second iteration: mid = (0 + 6 + 1) / 2 = 3
- Check if any special substring of length 3 appears ≥ 3 times
- Scanning for runs:
"aaa"
at position 0: contributes3-3+1 = 1
substring"aaa"
"b"
at position 3: contributes0
substrings"aaa"
at position 4: contributes1
substring"aaa"
"b"
at position 7: contributes0
substrings"c"
at position 8: contributes0
substrings"aaa"
at position 9: contributes1
substring"aaa"
"b"
at position 12: contributes0
substrings
- Count:
'a' → 3 occurrences
of length-3 special substrings check(3) = true
→ Update:l = 3
Third iteration: mid = (3 + 6 + 1) / 2 = 5
- Check if any special substring of length 5 appears ≥ 3 times
- All runs have length ≤ 3, so no length-5 special substrings exist
check(5) = false
→ Update:r = 4
Fourth iteration: mid = (3 + 4 + 1) / 2 = 4
- Check if any special substring of length 4 appears ≥ 3 times
- All runs have length ≤ 3, so no length-4 special substrings exist
check(4) = false
→ Update:r = 3
Step 3: Binary Search Termination
l = 3
,r = 3
→ loop ends- Return
l = 3
The answer is 3 because "aaa"
appears exactly 3 times in the string, making it the longest special substring that meets the requirement.
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 Args:
10 s: Input string
11
12 Returns:
13 Maximum length of special substring occurring at least 3 times, or -1 if none exists
14 """
15
16 def can_find_substring_with_length(target_length: int) -> bool:
17 """
18 Check if there exists a special substring of given length that appears at least 3 times.
19
20 Args:
21 target_length: The length of substring to check
22
23 Returns:
24 True if such substring exists, False otherwise
25 """
26 # Count occurrences of each character's substrings of target_length
27 occurrence_count = defaultdict(int)
28
29 # Iterate through consecutive character groups
30 i = 0
31 while i < string_length:
32 # Find the end of current group of same characters
33 j = i + 1
34 while j < string_length and s[j] == s[i]:
35 j += 1
36
37 # Calculate how many substrings of target_length can be formed
38 # from this group of (j - i) consecutive same characters
39 group_length = j - i
40 possible_substrings = max(0, group_length - target_length + 1)
41 occurrence_count[s[i]] += possible_substrings
42
43 # Move to next group
44 i = j
45
46 # Check if any character has at least 3 substrings of target_length
47 return max(occurrence_count.values()) >= 3
48
49 # Binary search for the maximum valid length
50 string_length = len(s)
51 left, right = 0, string_length
52
53 while left < right:
54 # Use upper mid to bias towards larger values
55 mid = (left + right + 1) >> 1
56
57 if can_find_substring_with_length(mid):
58 # If mid works, try to find a larger length
59 left = mid
60 else:
61 # If mid doesn't work, try smaller lengths
62 right = mid - 1
63
64 # Return -1 if no valid substring exists, otherwise return the maximum length
65 return -1 if left == 0 else left
66
1class 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 * @param s the input string
10 * @return the maximum length of special substring appearing at least 3 times, or -1 if none exists
11 */
12 public int maximumLength(String s) {
13 this.inputString = s;
14 this.stringLength = s.length();
15
16 // Binary search on the length of the special substring
17 int left = 0;
18 int right = stringLength;
19
20 while (left < right) {
21 // Calculate mid point, rounding up to avoid infinite loop
22 int mid = (left + right + 1) >> 1;
23
24 // Check if a special substring of length 'mid' appears at least 3 times
25 if (canFindSpecialSubstring(mid)) {
26 // If found, search for potentially longer substrings
27 left = mid;
28 } else {
29 // If not found, search for shorter substrings
30 right = mid - 1;
31 }
32 }
33
34 // Return -1 if no valid special substring found, otherwise return the length
35 return left == 0 ? -1 : left;
36 }
37
38 /**
39 * Checks if there exists a special substring of given length that appears at least 3 times.
40 *
41 * @param targetLength the length of special substring to check
42 * @return true if such substring exists at least 3 times, false otherwise
43 */
44 private boolean canFindSpecialSubstring(int targetLength) {
45 // Array to count occurrences of special substrings for each character
46 int[] occurrenceCount = new int[26];
47
48 // Iterate through the string to find consecutive character segments
49 for (int i = 0; i < stringLength;) {
50 int j = i + 1;
51
52 // Find the end of current segment of identical characters
53 while (j < stringLength && inputString.charAt(j) == inputString.charAt(i)) {
54 j++;
55 }
56
57 // Get the character index (0-25 for 'a'-'z')
58 int charIndex = inputString.charAt(i) - 'a';
59
60 // Calculate how many special substrings of length 'targetLength'
61 // can be formed from this segment of length (j - i)
62 // Formula: max(0, segmentLength - targetLength + 1)
63 occurrenceCount[charIndex] += Math.max(0, j - i - targetLength + 1);
64
65 // Check if we've found at least 3 occurrences
66 if (occurrenceCount[charIndex] >= 3) {
67 return true;
68 }
69
70 // Move to the next segment
71 i = j;
72 }
73
74 return false;
75 }
76}
77
1class Solution {
2public:
3 int maximumLength(string s) {
4 int stringLength = s.size();
5 int left = 0, right = stringLength;
6
7 // Lambda function to check if a substring of length 'targetLength' appears at least 3 times
8 auto canFormThreeSpecialSubstrings = [&](int targetLength) {
9 // Array to count occurrences of special substrings for each character
10 int occurrenceCount[26]{};
11
12 // Iterate through the string to find consecutive same-character segments
13 for (int i = 0; i < stringLength;) {
14 // Find the end of current segment of same characters
15 int j = i + 1;
16 while (j < stringLength && s[j] == s[i]) {
17 ++j;
18 }
19
20 // Calculate how many special substrings of length 'targetLength'
21 // can be formed from this segment
22 int charIndex = s[i] - 'a';
23 int segmentLength = j - i;
24 // A segment of length L can form (L - targetLength + 1) substrings of length targetLength
25 occurrenceCount[charIndex] += max(0, segmentLength - targetLength + 1);
26
27 // Check if we've found at least 3 occurrences
28 if (occurrenceCount[charIndex] >= 3) {
29 return true;
30 }
31
32 // Move to the next segment
33 i = j;
34 }
35 return false;
36 };
37
38 // Binary search for the maximum length of special substring that appears at least 3 times
39 while (left < right) {
40 // Use upper bound binary search (round up)
41 int mid = (left + right + 1) >> 1;
42
43 if (canFormThreeSpecialSubstrings(mid)) {
44 // If we can form 3 substrings of length mid, try longer lengths
45 left = mid;
46 } else {
47 // If we cannot, try shorter lengths
48 right = mid - 1;
49 }
50 }
51
52 // Return -1 if no valid special substring exists, otherwise return the maximum length
53 return left == 0 ? -1 : left;
54 }
55};
56
1/**
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 * @param s - The input string
5 * @returns The maximum length of qualifying substring, or -1 if none exists
6 */
7function maximumLength(s: string): number {
8 const stringLength: number = s.length;
9 let left: number = 0;
10 let right: number = stringLength;
11
12 /**
13 * Checks if there exists a substring of length targetLength that appears at least 3 times
14 * @param targetLength - The length to check for
15 * @returns True if such substring exists, false otherwise
16 */
17 const canFindSubstringOfLength = (targetLength: number): boolean => {
18 // Array to count occurrences for each character (26 letters)
19 const characterCounts: number[] = Array(26).fill(0);
20
21 // Iterate through the string to find consecutive character segments
22 let currentIndex: number = 0;
23 while (currentIndex < stringLength) {
24 // Find the end of current segment with same character
25 let nextIndex: number = currentIndex + 1;
26 while (nextIndex < stringLength && s[nextIndex] === s[currentIndex]) {
27 nextIndex++;
28 }
29
30 // Calculate the character index (0-25 for a-z)
31 const characterIndex: number = s[currentIndex].charCodeAt(0) - 'a'.charCodeAt(0);
32
33 // Count how many substrings of targetLength can be formed from this segment
34 // Formula: max(0, segmentLength - targetLength + 1)
35 const segmentLength: number = nextIndex - currentIndex;
36 const possibleSubstrings: number = Math.max(0, segmentLength - targetLength + 1);
37 characterCounts[characterIndex] += possibleSubstrings;
38
39 // Check if we found at least 3 occurrences
40 if (characterCounts[characterIndex] >= 3) {
41 return true;
42 }
43
44 // Move to the next segment
45 currentIndex = nextIndex;
46 }
47
48 return false;
49 };
50
51 // Binary search for the maximum valid length
52 while (left < right) {
53 // Calculate mid point (ceiling division to avoid infinite loop)
54 const mid: number = (left + right + 1) >> 1;
55
56 if (canFindSubstringOfLength(mid)) {
57 // If valid, search for potentially larger length
58 left = mid;
59 } else {
60 // If invalid, search for smaller length
61 right = mid - 1;
62 }
63 }
64
65 // Return -1 if no valid substring found, otherwise return the maximum length
66 return left === 0 ? -1 : left;
67}
68
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. Incorrect Counting of Overlapping Substrings
Pitfall: A common mistake is incorrectly counting how many special substrings of length x
can be formed from a consecutive run of the same character. Developers often forget that substrings can overlap.
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
(suggests only 2 substrings) - Correct count: We actually have 3 overlapping substrings:
"aa"
at positions [0:2], [1:3], [2:4]
Correct approach:
# CORRECT: Using sliding window formula
possible_substrings = max(0, group_length - target_length + 1)
2. Binary Search Boundary Issues
Pitfall: Using the wrong binary search template or incorrect mid calculation can lead to infinite loops or missing the correct answer.
Common mistakes:
# WRONG: May cause infinite loop when l+1 == r mid = (left + right) // 2 # WRONG: May miss the maximum valid value while left <= right: # Using <= instead of < mid = (left + right) // 2 if check(mid): left = mid + 1 # Skipping the valid answer else: right = mid - 1
Correct approach:
# CORRECT: Upper mid formula prevents infinite loops mid = (left + right + 1) >> 1 # or (left + right + 1) // 2 # CORRECT: Maintains invariant that left is always valid (or 0) while left < right: mid = (left + right + 1) >> 1 if check(mid): left = mid # Keep the valid answer else: right = mid - 1
3. Forgetting Edge Cases
Pitfall: Not handling the case where no valid substring exists.
Wrong:
# WRONG: Always returns a positive number return left # What if left is still 0?
Correct:
# CORRECT: Check if we found any valid substring return -1 if left == 0 else left
4. Inefficient Character Group Detection
Pitfall: Using nested loops or regex to find consecutive character groups, leading to O(n²) complexity.
Inefficient approach:
# WRONG: O(n²) time complexity
for i in range(n):
for j in range(i+1, n+1):
if all(s[k] == s[i] for k in range(i, j)):
# Process group
Efficient approach:
# CORRECT: O(n) single pass with two pointers i = 0 while i < n: j = i + 1 while j < n and s[j] == s[i]: j += 1 # Process group from i to j-1 i = j
Complete Corrected Solution:
from collections import defaultdict
class Solution:
def maximumLength(self, s: str) -> int:
def check(x):
if x == 0: # Edge case: length 0 is invalid
return False
cnt = defaultdict(int)
i = 0
n = len(s)
while i < n:
j = i + 1
while j < n and s[j] == s[i]:
j += 1
# Correct sliding window count
group_length = j - i
cnt[s[i]] += max(0, group_length - x + 1)
i = j
return cnt and max(cnt.values()) >= 3 # Handle empty cnt
n = len(s)
left, right = 0, n
# Correct binary search with upper mid
while left < right:
mid = (left + right + 1) >> 1
if check(mid):
left = mid
else:
right = mid - 1
# Handle no valid substring case
return -1 if left == 0 else left
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!