Facebook Pixel

2981. Find Longest Special Substring That Occurs Thrice I

MediumHash TableStringBinary SearchCountingSliding Window
Leetcode Link

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:

  1. Find all possible special substrings in the string s
  2. Identify which of these special substrings occur at least 3 times
  3. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 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:

  1. Binary search on the length of the special substring (from 0 to n)
  2. For each candidate length, scan through the string to find all runs of identical characters
  3. Count how many special substrings of that length each run contributes
  4. 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:

  1. Initialize a counter: Use defaultdict(int) to count occurrences of special substrings for each character.

  2. 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].

  3. Count special substrings in each run:

    • For a run from position i to j-1 (length = j - i)
    • Number of special substrings of length x = max(0, j - i - x + 1)
    • Add this count to cnt[s[i]]
  4. 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) → contributes 3 - 2 + 1 = 2 substrings of "aa"
  • Second run: "bbb" (length 3) → contributes 3 - 2 + 1 = 2 substrings of "bb"
  • Third run: "ccc" (length 3) → contributes 3 - 2 + 1 = 2 substrings of "cc"
  • None appear 3 times, so check(2) returns false

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 Evaluator

Example 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): contributes max(0, 3-7+1) = 0 substrings
    • "b" (length 1): contributes 0 substrings
    • "aaa" (length 3): contributes 0 substrings
    • "b" (length 1): contributes 0 substrings
    • "c" (length 1): contributes 0 substrings
    • "aaa" (length 3): contributes 0 substrings
    • "b" (length 1): contributes 0 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: contributes 3-3+1 = 1 substring "aaa"
    • "b" at position 3: contributes 0 substrings
    • "aaa" at position 4: contributes 1 substring "aaa"
    • "b" at position 7: contributes 0 substrings
    • "c" at position 8: contributes 0 substrings
    • "aaa" at position 9: contributes 1 substring "aaa"
    • "b" at position 12: contributes 0 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More