Facebook Pixel

2982. Find Longest Special Substring That Occurs Thrice II

MediumHash TableStringBinary SearchCountingSliding Window
Leetcode Link

Problem Description

You are given a string s consisting of lowercase English letters.

A special string is defined as a string made up of only a single character. For example:

  • "abc" is not special (contains different characters)
  • "ddd" is special (all characters are 'd')
  • "zz" is special (all characters are 'z')
  • "f" is special (single character)

Your task is to find the length of the longest special substring of s that occurs at least three times in the string.

A substring is a contiguous non-empty sequence of characters within a string.

Return:

  • The length of the longest special substring that appears at least 3 times
  • -1 if no such special substring exists

For example, if you have a string where the substring "aa" appears 3 or more times in different positions, and this is the longest such special substring, you would return 2.

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 we can simply trim one character from each occurrence of the length x substring to get occurrences of length x-1.

For example, if "aaa" appears 3 times in the string, 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 a certain length that appear 3+ times, we should try longer lengths. If we can't, we should try shorter lengths.

For checking if a given length x works, we need to count how many times special substrings of length x appear. We can do this efficiently by:

  1. Finding all maximal runs of the same character (like "aaaa" or "bbb")
  2. For each run of length L, we can extract max(0, L - x + 1) special substrings of length x

For instance, from "aaaaa" (length 5), we can extract:

  • If x = 3: we get 3 substrings ("aaa" starting at positions 0, 1, and 2)
  • If x = 4: we get 2 substrings ("aaaa" starting at positions 0 and 1)
  • If x = 6: we get 0 substrings (run is too short)

By counting these for each character and checking if any character's count reaches 3, we can determine if length x is achievable.

Learn more about Binary Search and Sliding Window patterns.

Solution Approach

The solution uses binary search combined with a sliding window counting technique.

Binary Search Setup:

  • Initialize search boundaries: l = 0 (left) and r = n (right), where n is the string length
  • For each iteration, calculate mid = (l + r + 1) >> 1 (equivalent to (l + r + 1) // 2)
  • If a special substring of length mid exists with 3+ occurrences, update l = mid (search for longer)
  • Otherwise, update r = mid - 1 (search for shorter)
  • Continue until l >= r

The Check Function: The check(x) function determines if there exists a special substring of length x appearing at least 3 times.

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

  2. Find maximal runs: Use two pointers i and j to identify consecutive runs of the same character:

    i = 0
    while i < n:
        j = i + 1
        while j < n and s[j] == s[i]:
            j += 1
        # Now s[i:j] is a maximal run of the same character
  3. Count substrings: For each maximal run from position i to j-1:

    • The run has length j - i
    • From this run, we can extract max(0, j - i - x + 1) special substrings of length x
    • Add this count to cnt[s[i]]
  4. Check threshold: After processing all runs, return True if any character has cnt[character] >= 3

Final Result:

  • If l == 0 after binary search, no valid special substring exists, return -1
  • Otherwise, return l as the maximum length

Example Walkthrough: For string "aaabbbccc" checking length x = 2:

  • Run "aaa" (length 3): contributes 3 - 2 + 1 = 2 substrings of "aa"
  • Run "bbb" (length 3): contributes 3 - 2 + 1 = 2 substrings of "bb"
  • Run "ccc" (length 3): contributes 3 - 2 + 1 = 2 substrings of "cc"
  • None reach count of 3, so check(2) returns False

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 the string s = "aaaa".

Step 1: Binary Search Initialization

  • String length n = 4
  • Search boundaries: l = 0, r = 4

Step 2: First Binary Search Iteration

  • Calculate mid = (0 + 4 + 1) >> 1 = 2
  • Check if special substrings of length 2 appear at least 3 times

Checking length 2:

  • Find maximal run: "aaaa" (positions 0 to 3)
  • Run length = 4
  • Number of length-2 substrings: max(0, 4 - 2 + 1) = 3
  • These are: "aa" at position 0, "aa" at position 1, "aa" at position 2
  • Count for character 'a': 3
  • Since 3 ≥ 3, check(2) returns True
  • Update l = 2 (search for longer substrings)

Step 3: Second Binary Search Iteration

  • New boundaries: l = 2, r = 4
  • Calculate mid = (2 + 4 + 1) >> 1 = 3
  • Check if special substrings of length 3 appear at least 3 times

Checking length 3:

  • Find maximal run: "aaaa" (positions 0 to 3)
  • Run length = 4
  • Number of length-3 substrings: max(0, 4 - 3 + 1) = 2
  • These are: "aaa" at position 0, "aaa" at position 1
  • Count for character 'a': 2
  • Since 2 < 3, check(3) returns False
  • Update r = 3 - 1 = 2

Step 4: Binary Search Termination

  • Now l = 2, r = 2
  • Since l >= r, binary search ends
  • Return l = 2 as the answer

The longest special substring that appears at least 3 times is "aa" with length 2.


Another Example: s = "abcaba"

Binary Search Process:

  • Initial: l = 0, r = 6

  • mid = 3: Check length 3

    • Maximal runs: "a", "b", "c", "a", "b", "a"
    • Each run has length 1, so max(0, 1 - 3 + 1) = 0 substrings
    • check(3) returns False
    • Update r = 2
  • mid = 1: Check length 1

    • Maximal runs contribute:
      • "a": 1 substring (appears at position 0)
      • "b": 1 substring (appears at position 1)
      • "c": 1 substring (appears at position 2)
      • "a": 1 substring (appears at position 3)
      • "b": 1 substring (appears at position 4)
      • "a": 1 substring (appears at position 5)
    • Count: 'a' appears 3 times, 'b' appears 2 times, 'c' appears 1 time
    • Since 'a' count = 3 ≥ 3, check(1) returns True
    • Update l = 1
  • Binary search ends with l = 1

  • Return 1 (the special substring "a" appears 3 times)

Solution Implementation

1from collections import defaultdict
2
3class Solution:
4    def maximumLength(self, s: str) -> int:
5        def can_find_substring_with_length(target_length: int) -> bool:
6            """
7            Check if there exists a substring of given length that appears at least 3 times.
8            The substring must consist of the same character repeated.
9          
10            Args:
11                target_length: The length of substring to check
12              
13            Returns:
14                True if such substring exists at least 3 times, False otherwise
15            """
16            # Count occurrences of each character's substring of target_length
17            occurrence_count = defaultdict(int)
18          
19            # Iterate through groups of consecutive same characters
20            i = 0
21            while i < string_length:
22                # Find the end of current group of same characters
23                j = i + 1
24                while j < string_length and s[j] == s[i]:
25                    j += 1
26              
27                # For a group of length (j-i), we can extract multiple substrings
28                # of target_length. The number of such substrings is:
29                # max(0, group_length - target_length + 1)
30                group_length = j - i
31                possible_substrings = max(0, group_length - target_length + 1)
32                occurrence_count[s[i]] += possible_substrings
33              
34                # Move to the next group
35                i = j
36          
37            # Check if any character's substring appears at least 3 times
38            return max(occurrence_count.values()) >= 3
39      
40        # Binary search for the maximum length
41        string_length = len(s)
42        left, right = 0, string_length
43      
44        # Binary search to find the maximum valid length
45        while left < right:
46            # Use ceiling division to bias towards larger values
47            mid = (left + right + 1) >> 1
48          
49            if can_find_substring_with_length(mid):
50                # If mid is valid, search for larger lengths
51                left = mid
52            else:
53                # If mid is invalid, search for smaller lengths
54                right = mid - 1
55      
56        # Return -1 if no valid substring exists, otherwise return the maximum length
57        return -1 if left == 0 else left
58
1class Solution {
2    private String inputString;
3    private int stringLength;
4
5    /**
6     * Finds the maximum length of a special substring that occurs at least 3 times.
7     * A special substring consists of only one repeated character (e.g., "aaa", "bb").
8     * 
9     * @param s the input string to analyze
10     * @return the maximum length of special substring occurring 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 (ceiling division to avoid infinite loop)
22            int mid = (left + right + 1) >> 1;
23          
24            if (check(mid)) {
25                // If a special substring of length 'mid' occurs at least 3 times,
26                // try to find a longer one
27                left = mid;
28            } else {
29                // If not possible with length 'mid', try shorter lengths
30                right = mid - 1;
31            }
32        }
33      
34        // Return -1 if no valid special substring found, otherwise return the maximum length
35        return left == 0 ? -1 : left;
36    }
37
38    /**
39     * Checks if there exists a special substring of given length that occurs at least 3 times.
40     * 
41     * @param targetLength the length of special substring to check for
42     * @return true if such a substring exists at least 3 times, false otherwise
43     */
44    private boolean check(int targetLength) {
45        // Count array for each letter (a-z)
46        int[] occurrenceCount = new int[26];
47      
48        // Iterate through consecutive groups of same characters
49        for (int startIndex = 0; startIndex < stringLength;) {
50            // Find the end of current group of consecutive same characters
51            int endIndex = startIndex + 1;
52            while (endIndex < stringLength && 
53                   inputString.charAt(endIndex) == inputString.charAt(startIndex)) {
54                endIndex++;
55            }
56          
57            // Get the character index (0 for 'a', 1 for 'b', etc.)
58            int charIndex = inputString.charAt(startIndex) - 'a';
59          
60            // Calculate how many substrings of length 'targetLength' can be formed
61            // from this group of consecutive characters
62            // If group length is L, we can form max(0, L - targetLength + 1) substrings
63            occurrenceCount[charIndex] += Math.max(0, endIndex - startIndex - targetLength + 1);
64          
65            // If any character's special substring of given length occurs >= 3 times, return true
66            if (occurrenceCount[charIndex] >= 3) {
67                return true;
68            }
69          
70            // Move to the next group of consecutive characters
71            startIndex = endIndex;
72        }
73      
74        return false;
75    }
76}
77
1class Solution {
2public:
3    int maximumLength(string s) {
4        int n = s.size();
5        int left = 0, right = n;
6      
7        // Lambda function to check if a substring of length 'length' appears at least 3 times
8        auto canFormThreeSubstrings = [&](int length) {
9            // Count array for each character (a-z)
10            int charCount[26]{};
11          
12            // Iterate through the string to find consecutive same characters
13            for (int i = 0; i < n;) {
14                // Find the end of current consecutive character sequence
15                int j = i + 1;
16                while (j < n && s[j] == s[i]) {
17                    ++j;
18                }
19              
20                // Get the character index (0-25 for a-z)
21                int charIndex = s[i] - 'a';
22              
23                // Calculate how many substrings of 'length' can be formed from this sequence
24                // If sequence length is j-i, we can form max(0, j-i-length+1) substrings
25                charCount[charIndex] += max(0, j - i - length + 1);
26              
27                // If we can form at least 3 substrings, return true
28                if (charCount[charIndex] >= 3) {
29                    return true;
30                }
31              
32                // Move to the next different character
33                i = j;
34            }
35            return false;
36        };
37      
38        // Binary search for the maximum length
39        while (left < right) {
40            // Use (left + right + 1) / 2 to avoid infinite loop when left = right - 1
41            int mid = (left + right + 1) >> 1;
42          
43            if (canFormThreeSubstrings(mid)) {
44                // If current length works, try larger lengths
45                left = mid;
46            } else {
47                // If current length doesn't work, try smaller lengths
48                right = mid - 1;
49            }
50        }
51      
52        // Return -1 if no valid substring found, 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 each substring consists of the same character
4 * @param s - The input string
5 * @returns The maximum length of valid substring, or -1 if no such substring 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 a substring of given length appears at least 3 times
14     * @param targetLength - The length to check
15     * @returns True if valid substring of this length exists, false otherwise
16     */
17    const canFormValidSubstring = (targetLength: number): boolean => {
18        // Count array for each letter (26 letters in alphabet)
19        const letterCounts: number[] = Array(26).fill(0);
20      
21        // Iterate through consecutive segments of same characters
22        for (let startIndex = 0; startIndex < stringLength; ) {
23            let endIndex = startIndex + 1;
24          
25            // Find the end of current segment with same character
26            while (endIndex < stringLength && s[endIndex] === s[startIndex]) {
27                endIndex++;
28            }
29          
30            // Calculate the letter index (0-25 for a-z)
31            const letterIndex: number = s[startIndex].charCodeAt(0) - 'a'.charCodeAt(0);
32          
33            // Count how many substrings of targetLength can be formed from this segment
34            // If segment length is L, we can form max(0, L - targetLength + 1) substrings
35            letterCounts[letterIndex] += Math.max(0, endIndex - startIndex - targetLength + 1);
36          
37            // Check if we have at least 3 occurrences of this substring
38            if (letterCounts[letterIndex] >= 3) {
39                return true;
40            }
41          
42            // Move to next segment
43            startIndex = endIndex;
44        }
45      
46        return false;
47    };
48  
49    // Binary search for the maximum valid length
50    while (left < right) {
51        // Use ceiling division to avoid infinite loop
52        const middle: number = (left + right + 1) >> 1;
53      
54        if (canFormValidSubstring(middle)) {
55            // Valid length found, search for larger lengths
56            left = middle;
57        } else {
58            // Invalid length, search for smaller lengths
59            right = middle - 1;
60        }
61    }
62  
63    // Return -1 if no valid substring exists, otherwise return the maximum length
64    return left === 0 ? -1 : left;
65}
66

Time and Space Complexity

Time Complexity: O(n × log n)

The algorithm uses binary search on the answer range [0, 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 character segments
  • For each segment, it performs constant time operations to update the count in the dictionary
  • Finding the maximum value in the dictionary takes O(|Σ|) time where |Σ| = 26 for lowercase letters

Since |Σ| is constant (26), the time per check call is O(n). Therefore, the total time complexity is O(n × log n).

Space Complexity: O(|Σ|) or O(1)

The space is used by:

  • The cnt dictionary in the check function, which stores at most |Σ| entries (one for each distinct character in the string)
  • Since we're dealing with lowercase English letters, |Σ| = 26, making this O(26) = O(1) constant space
  • A few integer variables (i, j, l, r, mid) which use O(1) space

Therefore, the space complexity is O(|Σ|) where |Σ| = 26, which simplifies to O(1).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding "Special Substring" Definition

Many developers incorrectly assume that we need to find any substring that appears 3+ times, rather than a substring consisting of only repeated characters.

Wrong Interpretation:

# Incorrectly counts ALL substrings, not just special ones
def check_wrong(x):
    substrings = defaultdict(int)
    for i in range(len(s) - x + 1):
        substrings[s[i:i+x]] += 1
    return max(substrings.values()) >= 3

Correct Approach: Only count substrings where all characters are the same. The solution correctly handles this by processing maximal runs of identical characters.

2. Off-by-One Error in Counting Substrings

When calculating how many substrings of length x can be extracted from a run of length n, the formula is max(0, n - x + 1), not n - x.

Common Mistake:

# Missing the +1 in the formula
possible_substrings = max(0, group_length - target_length)  # Wrong!

Correct Formula:

possible_substrings = max(0, group_length - target_length + 1)

For example, from "aaa" (length 3), we can extract:

  • 2 substrings of length 2: "aa" at positions [0,1] and [1,2]
  • Formula: 3 - 2 + 1 = 2

3. Binary Search Boundary Issues

Using standard binary search pattern mid = (left + right) // 2 can cause infinite loops when searching for maximum valid values.

Problematic Pattern:

while left < right:
    mid = (left + right) // 2  # Can cause infinite loop
    if check(mid):
        left = mid  # When left = right - 1, mid always equals left
    else:
        right = mid - 1

Solution: Use ceiling division mid = (left + right + 1) // 2 or the bit shift equivalent (left + right + 1) >> 1 to ensure progress when left = right - 1.

4. Inefficient Character Group Detection

Processing each character individually instead of identifying maximal runs leads to incorrect counting.

Inefficient/Wrong:

for i in range(len(s)):
    if i == 0 or s[i] != s[i-1]:
        # Start new group - but doesn't track full group length
        count = 1
    else:
        count += 1
    # Can't correctly calculate substrings without knowing total group length

Correct Two-Pointer Approach:

i = 0
while i < len(s):
    j = i + 1
    while j < len(s) and s[j] == s[i]:
        j += 1
    # Now we know the complete group s[i:j] before processing
    group_length = j - i
    # Process entire group at once
    i = j

5. Forgetting Edge Cases

Not handling the case where no valid special substring exists (should return -1).

Missing Edge Case Handling:

# Wrong: always returns a value even when no valid substring exists
return left

Correct Implementation:

return -1 if left == 0 else left

This ensures that when binary search converges to 0 (meaning even length-1 special substrings don't appear 3+ times), we correctly return -1.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

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

Load More