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

For checking if a given length x works, we find all maximal runs of the same character and count substrings efficiently. 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 3 substrings of length 3 (starting at positions 0, 1, and 2).

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
61
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    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}
68
1class 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};
55
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 */
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}
62

Solution Approach

The solution uses 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:

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

  2. Find maximal runs using two pointers:

    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
  3. Count substrings: For each run of length L = j - i, we can extract max(0, L - x + 1) special substrings of length x

  4. Return the inverse: Return True if no character has 3+ occurrences (infeasible)

Final Result

After binary search completes:

  • If first_true_index == 1, even length 1 doesn't work, return -1
  • Otherwise, return first_true_index - 1 as the maximum valid length

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
  • Binary search range: left = 1, right = 4
  • first_true_index = -1 (tracks first infeasible length)

Step 2: Binary Search Iterations

First iteration: mid = (1 + 4) / 2 = 2

  • Check if length 2 is infeasible (no 3+ occurrences)
  • Run "aaaa" (length 4): contributes 4 - 2 + 1 = 3 substrings
  • Count for 'a': 3 ≥ 3 → length 2 is feasible
  • infeasible = False
  • Update: left = 3

Second iteration: mid = (3 + 4) / 2 = 3

  • Check if length 3 is infeasible
  • Run "aaaa" (length 4): contributes 4 - 3 + 1 = 2 substrings
  • Count for 'a': 2 < 3 → length 3 is infeasible
  • infeasible = True
  • Update: first_true_index = 3, right = 2

Step 3: Binary Search Termination

  • left = 3 > right = 2 → loop ends
  • first_true_index = 3 (first infeasible length)
  • Return first_true_index - 1 = 2

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


Another Example: s = "abcaba"

Binary Search Process:

  • Initial: left = 1, right = 6, first_true_index = -1

mid = 3: Check length 3

  • All runs have length 1, so max(0, 1 - 3 + 1) = 0 substrings
  • infeasible = True
  • Update: first_true_index = 3, right = 2

mid = 1: Check length 1

  • Each run contributes 1 substring
  • Count: 'a' = 3, 'b' = 2, 'c' = 1
  • 'a' has 3 ≥ 3 → length 1 is feasible
  • infeasible = False
  • Update: left = 2

mid = 2: Check length 2

  • All runs have length 1 < 2, so 0 substrings

  • infeasible = True

  • Update: first_true_index = 2, right = 1

  • left = 2 > right = 1 → loop ends

  • Return first_true_index - 1 = 1

The special substring "a" appears 3 times.

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. 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
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

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

Load More