Facebook Pixel

1156. Swap For Longest Repeated Character Substring

MediumHash TableStringSliding Window
Leetcode Link

Problem Description

You are given a string text consisting of characters. You can perform at most one swap operation where you choose any two characters in the string and exchange their positions.

Your task is to find the length of the longest possible substring that contains only repeated characters (all characters in the substring are the same) after performing at most one swap.

For example, if you have the string "ababa", you could swap the character at position 1 ('b') with the character at position 2 ('a') to get "aaabb". This would give you a substring "aaa" of length 3 with repeated characters.

The key insight is that you want to maximize the length of consecutive identical characters by potentially swapping one character from elsewhere in the string to extend or connect groups of the same character.

The algorithm works by examining each group of consecutive identical characters and checking if it can be extended by:

  1. Adding one more character of the same type from elsewhere in the string
  2. Connecting it with another group of the same character that's separated by exactly one different character (by swapping that different character out)

The maximum possible length for any character c is limited by the total count of that character in the entire string, since you cannot create more instances of a character than what exists in the original string.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we think about maximizing the length of repeated characters with one swap, we need to consider what patterns we can create or extend through swapping.

Let's think about the possible scenarios where a swap would be beneficial:

  1. Extending a group: If we have a group like "aaa" and there's another 'a' somewhere else in the string, we can swap that 'a' to be adjacent to our group, making it "aaaa".

  2. Bridging two groups: If we have two groups of the same character separated by a single different character, like "aaa_b_aa", we can swap that 'b' with an 'a' from elsewhere (if available) to create "aaaaaa".

  3. Filling a gap: Similar to bridging, but we might have "aa_b_a" and swap the 'b' out to connect them.

The key observation is that for any character, we want to find segments of consecutive occurrences and see if we can make them longer. When we encounter a segment like "aaa", we should check:

  • Is there another segment of 'a' characters nearby (separated by just one different character)?
  • If yes, can we connect them by swapping out the separating character?
  • If no nearby segment exists, can we at least add one more 'a' from elsewhere?

The crucial constraint is that we cannot create more of any character than what originally exists in the string. If we have only 5 'a' characters total in the string, the maximum length of consecutive 'a's we can achieve is 5, regardless of how we swap.

This leads us to a two-pointer approach: for each position, we find the current segment of identical characters, then look ahead to see if there's another segment of the same character just one position away. The maximum achievable length would be min(left_segment + right_segment + 1, total_count_of_that_character).

Learn more about Sliding Window patterns.

Solution Approach

The solution uses a two-pointer technique to efficiently find all segments of repeated characters and determine the maximum possible length after one swap.

First, we count the frequency of each character in the string using a Counter: cnt = Counter(text). This gives us the total occurrences of each character, which serves as an upper bound for the maximum possible length of any repeated character substring.

The main algorithm uses three pointers: i, j, and k:

  1. Finding the left segment: Starting from position i, we move pointer j to the right while text[j] == text[i]. This gives us a segment of length l = j - i where all characters are the same as text[i].

  2. Skipping the gap: After finding the left segment, position j points to a different character. We skip this character and set k = j + 1.

  3. Finding the right segment: We continue moving k to the right while text[k] == text[i]. This gives us another segment of the same character with length r = k - j - 1 (we subtract 1 because there's a gap character at position j).

  4. Calculating maximum length: For the current character text[i], the maximum achievable length is:

    • If we can bridge the two segments by swapping the gap character: l + r + 1
    • But this is limited by the total count of that character: min(l + r + 1, cnt[text[i]])

    The +1 accounts for either:

    • Swapping the gap character at position j with another instance of text[i] from elsewhere
    • Or if no right segment exists (r = 0), adding one more character of the same type to extend the left segment
  5. Moving to next segment: We update i = j to move to the next different character segment and repeat the process.

The algorithm continues until we've examined all positions in the string, keeping track of the maximum length found across all segments.

Time Complexity: O(n) where n is the length of the string, as each character is visited at most twice.

Space Complexity: O(1) or O(26) for the character counter (assuming lowercase English letters).

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 algorithm with the string text = "ababa".

Step 1: Count character frequencies

  • cnt = {'a': 3, 'b': 2}
  • This tells us the maximum possible length for 'a' is 3 and for 'b' is 2.

Step 2: Process segments starting from position 0

Iteration 1: i = 0

  • Current character: text[0] = 'a'
  • Find left segment: j moves from 0 to 1 (stops at 'b')
    • Left segment = "a", length l = 1 - 0 = 1
  • Skip gap: k = j + 1 = 2
  • Find right segment: k moves from 2 to 3 (text[2] = 'a', stops at text[3] = 'b')
    • Right segment = "a", length r = 3 - 1 - 1 = 1
  • Calculate max: min(1 + 1 + 1, cnt['a']) = min(3, 3) = 3
    • This represents swapping the 'b' at position 1 with an 'a' from position 4
  • Update: i = j = 1, ans = 3

Iteration 2: i = 1

  • Current character: text[1] = 'b'
  • Find left segment: j moves from 1 to 2 (stops at 'a')
    • Left segment = "b", length l = 2 - 1 = 1
  • Skip gap: k = j + 1 = 3
  • Find right segment: k moves from 3 to 4 (text[3] = 'b', stops at text[4] = 'a')
    • Right segment = "b", length r = 4 - 2 - 1 = 1
  • Calculate max: min(1 + 1 + 1, cnt['b']) = min(3, 2) = 2
    • Limited by total count of 'b' (only 2 'b's exist)
  • Update: i = j = 2, ans = max(3, 2) = 3

Iteration 3: i = 2

  • Current character: text[2] = 'a'
  • Find left segment: j moves from 2 to 3 (stops at 'b')
    • Left segment = "a", length l = 3 - 2 = 1
  • Skip gap: k = j + 1 = 4
  • Find right segment: k moves from 4 to 5 (text[4] = 'a', reaches end)
    • Right segment = "a", length r = 5 - 3 - 1 = 1
  • Calculate max: min(1 + 1 + 1, cnt['a']) = min(3, 3) = 3
  • Update: i = j = 3, ans = max(3, 3) = 3

Iteration 4: i = 3

  • Current character: text[3] = 'b'
  • Find left segment: j moves from 3 to 4 (stops at 'a')
    • Left segment = "b", length l = 4 - 3 = 1
  • Skip gap: k = j + 1 = 5 (out of bounds)
  • Find right segment: No right segment, r = 0
  • Calculate max: min(1 + 0 + 1, cnt['b']) = min(2, 2) = 2
    • The +1 represents adding another 'b' from position 1 to extend
  • Update: i = j = 4, ans = max(3, 2) = 3

Iteration 5: i = 4

  • Current character: text[4] = 'a'
  • Find left segment: j moves from 4 to 5 (reaches end)
    • Left segment = "a", length l = 5 - 4 = 1
  • No gap or right segment
  • Calculate max: min(1 + 0 + 1, cnt['a']) = min(2, 3) = 2
  • Update: i = j = 5, loop ends

Result: Maximum length = 3

This can be achieved by swapping text[1] with text[4], transforming "ababa" → "aaabb", creating "aaa" of length 3.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def maxRepOpt1(self, text: str) -> int:
5        # Count frequency of each character in the text
6        char_count = Counter(text)
7        n = len(text)
8        max_length = 0
9        i = 0
10      
11        # Iterate through each group of consecutive same characters
12        while i < n:
13            # Find the end of current consecutive group
14            j = i
15            while j < n and text[j] == text[i]:
16                j += 1
17          
18            # Calculate length of current consecutive group
19            left_group_length = j - i
20          
21            # Check if there's another group of same character after a gap of 1
22            k = j + 1
23            while k < n and text[k] == text[i]:
24                k += 1
25          
26            # Calculate length of the right group (after gap)
27            right_group_length = k - j - 1
28          
29            # Maximum possible length is either:
30            # 1. left + right + 1 (swap one character to connect groups)
31            # 2. Total count of this character (if we don't have enough chars)
32            max_possible = min(left_group_length + right_group_length + 1, 
33                             char_count[text[i]])
34            max_length = max(max_length, max_possible)
35          
36            # Move to next group
37            i = j
38          
39        return max_length
40
1class Solution {
2    public int maxRepOpt1(String text) {
3        // Count frequency of each character in the string
4        int[] charFrequency = new int[26];
5        int textLength = text.length();
6        for (int i = 0; i < textLength; i++) {
7            charFrequency[text.charAt(i) - 'a']++;
8        }
9      
10        int maxLength = 0;
11        int currentIndex = 0;
12      
13        // Iterate through the string to find consecutive character groups
14        while (currentIndex < textLength) {
15            // Find the end of current consecutive character group
16            int groupEndIndex = currentIndex;
17            while (groupEndIndex < textLength && 
18                   text.charAt(groupEndIndex) == text.charAt(currentIndex)) {
19                groupEndIndex++;
20            }
21          
22            // Calculate length of current consecutive group
23            int leftGroupLength = groupEndIndex - currentIndex;
24          
25            // Check if there's another group of same character after skipping one character
26            int nextGroupStartIndex = groupEndIndex + 1;
27            while (nextGroupStartIndex < textLength && 
28                   text.charAt(nextGroupStartIndex) == text.charAt(currentIndex)) {
29                nextGroupStartIndex++;
30            }
31          
32            // Calculate length of the right group (after the gap)
33            int rightGroupLength = nextGroupStartIndex - groupEndIndex - 1;
34          
35            // Update maximum length
36            // We can either:
37            // 1. Swap one character to connect two groups (leftGroupLength + rightGroupLength + 1)
38            // 2. But we're limited by total frequency of the character
39            char currentChar = text.charAt(currentIndex);
40            int totalFrequency = charFrequency[currentChar - 'a'];
41            maxLength = Math.max(maxLength, 
42                                Math.min(leftGroupLength + rightGroupLength + 1, totalFrequency));
43          
44            // Move to the next different character group
45            currentIndex = groupEndIndex;
46        }
47      
48        return maxLength;
49    }
50}
51
1class Solution {
2public:
3    int maxRepOpt1(string text) {
4        // Count frequency of each character in the string
5        int charFrequency[26] = {0};
6        for (char& ch : text) {
7            ++charFrequency[ch - 'a'];
8        }
9      
10        int textLength = text.size();
11        int maxLength = 0;
12        int currentIndex = 0;
13      
14        // Iterate through each group of consecutive same characters
15        while (currentIndex < textLength) {
16            // Find the end of current group of same characters
17            int groupEndIndex = currentIndex;
18            while (groupEndIndex < textLength && text[groupEndIndex] == text[currentIndex]) {
19                ++groupEndIndex;
20            }
21            int leftGroupLength = groupEndIndex - currentIndex;
22          
23            // Check if there's another group of the same character after skipping one character
24            int rightGroupStartIndex = groupEndIndex + 1;
25            while (rightGroupStartIndex < textLength && text[rightGroupStartIndex] == text[currentIndex]) {
26                ++rightGroupStartIndex;
27            }
28            int rightGroupLength = rightGroupStartIndex - groupEndIndex - 1;
29          
30            // Calculate maximum possible length:
31            // - Can combine left and right groups by swapping the middle character
32            // - Limited by total frequency of the character in the string
33            maxLength = max(maxLength, min(leftGroupLength + rightGroupLength + 1, 
34                                          charFrequency[text[currentIndex] - 'a']));
35          
36            // Move to the next group
37            currentIndex = groupEndIndex;
38        }
39      
40        return maxLength;
41    }
42};
43
1/**
2 * Finds the maximum length of substring after performing at most one character swap
3 * @param text - Input string containing only lowercase letters
4 * @returns Maximum length of repeating substring after one swap
5 */
6function maxRepOpt1(text: string): number {
7    // Helper function to convert character to index (0-25 for 'a'-'z')
8    const charToIndex = (char: string): number => {
9        return char.charCodeAt(0) - 'a'.charCodeAt(0);
10    };
11  
12    // Count frequency of each character in the string
13    const charFrequency: number[] = new Array(26).fill(0);
14    for (const char of text) {
15        charFrequency[charToIndex(char)]++;
16    }
17  
18    let maxLength: number = 0;
19    let currentIndex: number = 0;
20    const textLength: number = text.length;
21  
22    // Iterate through the string to find consecutive character segments
23    while (currentIndex < textLength) {
24        // Find the end of current consecutive segment
25        let segmentEnd: number = currentIndex;
26        while (segmentEnd < textLength && text[segmentEnd] === text[currentIndex]) {
27            segmentEnd++;
28        }
29      
30        // Calculate length of current segment
31        const leftSegmentLength: number = segmentEnd - currentIndex;
32      
33        // Check if there's another segment of same character after one different character
34        let nextSegmentEnd: number = segmentEnd + 1;
35        while (nextSegmentEnd < textLength && text[nextSegmentEnd] === text[currentIndex]) {
36            nextSegmentEnd++;
37        }
38      
39        // Calculate length of the segment after the gap
40        const rightSegmentLength: number = nextSegmentEnd - segmentEnd - 1;
41      
42        // Update maximum length considering:
43        // 1. We can combine left and right segments if there's a gap of 1
44        // 2. We can add 1 more if there's an extra character available elsewhere
45        // 3. We're limited by total count of this character in the string
46        const currentChar: string = text[currentIndex];
47        const totalAvailable: number = charFrequency[charToIndex(currentChar)];
48        const possibleLength: number = leftSegmentLength + rightSegmentLength + 1;
49        maxLength = Math.max(maxLength, Math.min(totalAvailable, possibleLength));
50      
51        // Move to next segment
52        currentIndex = segmentEnd;
53    }
54  
55    return maxLength;
56}
57

Time and Space Complexity

Time Complexity: O(n)

The algorithm uses a single main loop with variable i that traverses the string. Within each iteration:

  • The inner while loop with variable j finds the end of the current consecutive segment
  • The second inner while loop with variable k checks for matching characters after a gap

Although there are nested loops, each character in the string is visited at most twice (once by the j pointer and potentially once by the k pointer). The key observation is that i jumps directly to j after each iteration, meaning we don't revisit characters. Therefore, the amortized time complexity is O(n) where n is the length of the string.

Space Complexity: O(C)

The space is primarily used by the Counter object which stores the frequency of each unique character in the string. Since the problem deals with lowercase English letters, the maximum number of unique characters is 26. Therefore, the space complexity is O(C) where C = 26, which represents the size of the character set.

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

Common Pitfalls

1. Incorrectly handling single character groups

A common mistake is assuming there's always a right group after the gap. When we have a pattern like "aabaa" and we're at the last group of 'a', there's no right group, so right_group_length = 0. The code correctly handles this by using k - j - 1, which naturally gives 0 when k = j + 1.

2. Forgetting the character count constraint

A critical pitfall is calculating left + right + 1 without considering the total count limitation. For example, in "aabaaa", when examining the first group of 'a', we might think we can achieve length 6 (2 + 3 + 1), but we only have 5 'a's total. Always cap the result with min(..., char_count[text[i]]).

3. Off-by-one errors in length calculations

When calculating right_group_length, it's easy to make mistakes with the formula. Remember:

  • left_group_length = j - i (straightforward)
  • right_group_length = k - j - 1 (we subtract 1 for the gap character at position j)

Getting this wrong would incorrectly count the gap character as part of the right group.

4. Not handling edge cases properly

  • Single character string: The code handles this correctly since the while loops naturally terminate
  • String with all same characters: Like "aaaa", the maximum should be 4, not 5
  • No possible swap benefit: Like "abcdef", each character appears once, so max length is 1

5. Misunderstanding the "+1" in the formula

The +1 in left + right + 1 represents either:

  • A character from elsewhere being swapped into the gap position (when connecting two groups)
  • An additional character of the same type being swapped adjacent to a single group (when extending)

It does NOT mean we're creating a new character out of nowhere.

Solution to Avoid These Pitfalls:

# Add explicit comments and assertions to clarify logic
def maxRepOpt1(self, text: str) -> int:
    char_count = Counter(text)
    n = len(text)
    max_length = 0
    i = 0
  
    while i < n:
        # Find left group
        j = i
        while j < n and text[j] == text[i]:
            j += 1
        left_group_length = j - i
      
        # Find right group (may not exist)
        k = j + 1  # Skip the gap character at position j
        while k < n and text[k] == text[i]:
            k += 1
      
        # Calculate right group length
        # If no right group exists, this will be 0
        right_group_length = max(0, k - j - 1)  # Explicit max for clarity
      
        # The +1 accounts for swapping in one character
        # But we can't exceed total count of this character
        max_possible = min(
            left_group_length + right_group_length + 1,
            char_count[text[i]]
        )
      
        max_length = max(max_length, max_possible)
        i = j
      
    return max_length
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More