1156. Swap For Longest Repeated Character Substring
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:
- Adding one more character of the same type from elsewhere in the string
- 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.
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:
-
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"
. -
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"
. -
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
:
-
Finding the left segment: Starting from position
i
, we move pointerj
to the right whiletext[j] == text[i]
. This gives us a segment of lengthl = j - i
where all characters are the same astext[i]
. -
Skipping the gap: After finding the left segment, position
j
points to a different character. We skip this character and setk = j + 1
. -
Finding the right segment: We continue moving
k
to the right whiletext[k] == text[i]
. This gives us another segment of the same character with lengthr = k - j - 1
(we subtract 1 because there's a gap character at positionj
). -
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 oftext[i]
from elsewhere - Or if no right segment exists (
r = 0
), adding one more character of the same type to extend the left segment
- If we can bridge the two segments by swapping the gap character:
-
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 EvaluatorExample 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
- Left segment = "a", length
- 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
- Right segment = "a", length
- 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
- Left segment = "b", length
- 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
- Right segment = "b", length
- 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
- Left segment = "a", length
- 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
- Right segment = "a", length
- 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
- Left segment = "b", length
- 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
- Left segment = "a", length
- 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 variablej
finds the end of the current consecutive segment - The second inner
while
loop with variablek
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 positionj
)
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
Which data structure is used to implement priority queue?
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!