1750. Minimum Length of String After Deleting Similar Ends
Problem Description
You are given a string s
that consists only of the characters 'a'
, 'b'
, and 'c'
. You can repeatedly apply the following operation to the string:
- Choose a non-empty prefix of
s
where all characters in the prefix are the same (e.g., "aaa" from "aaabccc") - Choose a non-empty suffix of
s
where all characters in the suffix are the same (e.g., "ccc" from "aaabccc") - The prefix and suffix must not overlap (they cannot share any index position)
- The character in the prefix must be the same as the character in the suffix (e.g., both must be 'a', or both must be 'b', etc.)
- Delete both the prefix and suffix from the string
You can perform this operation any number of times (including zero times). Your goal is to find the minimum possible length of the string after applying these operations optimally.
For example, if s = "aabccabba"
:
- You can remove prefix "aa" and suffix "a" (both are 'a'), leaving "bccabb"
- Then remove prefix "b" and suffix "bb" (both are 'b'), leaving "cca"
- No more operations are possible since 'c' β 'a'
- The minimum length is 3
The solution uses two pointers starting from both ends of the string. When the characters at both pointers are equal, it expands to include all consecutive identical characters from both ends (finding the maximum prefix and suffix with the same character), then removes them by moving the pointers inward. This process continues until the characters at the two pointers are different or the pointers meet/cross.
Intuition
The key insight is that we want to remove as many characters as possible from both ends of the string, but we can only do so when the characters at both ends are the same.
Think about it this way: each operation removes a prefix and suffix with the same character. Once we perform an operation, we're left with a shorter string, and we can potentially perform another operation on this new string. The process naturally suggests working from the outside towards the center of the string.
Since we want the minimum length, we should be greedy - whenever we can remove characters from both ends (when they match), we should remove as many as possible. For example, if we have "aaabcaaa"
, when we see that both ends have 'a', we should remove all consecutive 'a's from both ends ("aaa"
from the left and "aaa"
from the right), not just one character at a time.
This leads us to a two-pointer approach:
- Start with pointers at both ends of the string
- When the characters at both pointers are the same, we have an opportunity to remove characters
- Expand each pointer to include all consecutive identical characters (find the entire prefix and suffix of the same character)
- Move the pointers past these regions to "remove" them
- Repeat until the characters at the pointers are different
The process stops when either:
- The characters at the two pointers are different (no more valid operations possible)
- The pointers meet or cross (the string has been reduced as much as possible)
The remaining characters between the pointers (if any) represent the minimum length string we can achieve, which is calculated as j - i + 1
. We use max(0, j - i + 1)
to handle the case where the pointers might cross, ensuring we don't return a negative length.
Learn more about Two Pointers patterns.
Solution Approach
The solution uses a two-pointer technique to efficiently find the minimum length of the string after all possible operations.
Implementation Details:
-
Initialize two pointers: Set
i = 0
pointing to the start of the string andj = len(s) - 1
pointing to the end of the string. -
Main loop condition: Continue the process while
i < j
ands[i] == s[j]
. This ensures:- The pointers haven't crossed or met (
i < j
) - The characters at both ends are the same, making a removal operation valid
- The pointers haven't crossed or met (
-
Expand the left pointer: When we find matching characters at both ends, we need to include all consecutive identical characters from the left:
while i + 1 < j and s[i] == s[i + 1]: i += 1
This loop moves
i
rightward to cover all characters equal tos[i]
. The conditioni + 1 < j
ensures we don't cross the right pointer. -
Expand the right pointer: Similarly, include all consecutive identical characters from the right:
while i < j - 1 and s[j - 1] == s[j]: j -= 1
This moves
j
leftward to cover all characters equal tos[j]
. The conditioni < j - 1
prevents crossing. -
Remove the prefix and suffix: After identifying the full prefix and suffix, we "remove" them by moving past them:
i, j = i + 1, j - 1
-
Calculate the result: After the loop ends (when characters at
i
andj
are different or pointers have crossed), the remaining string length isj - i + 1
. We usemax(0, j - i + 1)
to handle cases where all characters were removed (pointers crossed).
Example walkthrough with s = "aabccabba"
:
- Initially:
i=0, j=8
,s[0]='a', s[8]='a'
(equal, continue) - Expand left:
i
moves to 1 (covering "aa") - Expand right:
j
stays at 8 (only one 'a' at the end) - Remove:
i=2, j=7
- Now:
s[2]='b', s[7]='b'
(equal, continue) - Expand left:
i
stays at 2 (only one 'b') - Expand right:
j
moves to 6 (covering "bb") - Remove:
i=3, j=5
- Now:
s[3]='c', s[5]='a'
(not equal, stop) - Result:
5 - 3 + 1 = 3
The time complexity is O(n)
where n
is the length of the string, as each character is visited at most once. The space complexity is O(1)
as we only use two pointers.
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 trace through a small example: s = "aabaa"
Initial State:
- String:
"aabaa"
- Left pointer
i = 0
(pointing to 'a') - Right pointer
j = 4
(pointing to 'a')
Step 1: First Operation
- Check:
s[0] = 'a'
ands[4] = 'a'
β They match, so we can perform an operation - Expand left pointer to include all consecutive 'a's:
s[0] = 'a'
,s[1] = 'a'
β Movei
to position 1s[1] = 'a'
,s[2] = 'b'
β Stop expanding (different character)
- Expand right pointer to include all consecutive 'a's:
s[4] = 'a'
,s[3] = 'a'
β Movej
to position 3s[3] = 'a'
,s[2] = 'b'
β Stop expanding (different character)
- Remove the prefix "aa" and suffix "aa" by moving pointers:
i = 1 + 1 = 2
j = 3 - 1 = 2
Step 2: Check Loop Condition
- Now
i = 2
andj = 2
(pointers meet) - Since
i
is not less thanj
, exit the loop
Step 3: Calculate Result
- Remaining string length =
j - i + 1 = 2 - 2 + 1 = 1
- The remaining string is just "b" at position 2
Verification:
Starting with "aabaa"
, we removed prefix "aa" and suffix "aa", leaving us with "b". The minimum possible length is 1, which matches our calculation.
Solution Implementation
1class Solution:
2 def minimumLength(self, s: str) -> int:
3 """
4 Remove matching prefix and suffix characters from string to find minimum length.
5
6 Args:
7 s: Input string to process
8
9 Returns:
10 Minimum length after removing matching prefix and suffix characters
11 """
12 # Initialize two pointers at the start and end of string
13 left = 0
14 right = len(s) - 1
15
16 # Continue while pointers haven't crossed and characters at both ends match
17 while left < right and s[left] == s[right]:
18 # Store the current matching character
19 current_char = s[left]
20
21 # Move left pointer to skip all consecutive occurrences of current character
22 while left + 1 < right and s[left] == s[left + 1]:
23 left += 1
24
25 # Move right pointer to skip all consecutive occurrences of current character
26 while left < right - 1 and s[right - 1] == s[right]:
27 right -= 1
28
29 # Move both pointers inward by one position to continue checking
30 left += 1
31 right -= 1
32
33 # Calculate remaining length (ensure non-negative result)
34 return max(0, right - left + 1)
35
1class Solution {
2 /**
3 * Finds the minimum length of string after removing matching prefixes and suffixes.
4 * Repeatedly removes matching characters from both ends of the string.
5 *
6 * @param s the input string to process
7 * @return the minimum possible length after removing matching prefix-suffix pairs
8 */
9 public int minimumLength(String s) {
10 // Initialize two pointers at the start and end of the string
11 int left = 0;
12 int right = s.length() - 1;
13
14 // Continue while pointers haven't crossed and characters at both ends match
15 while (left < right && s.charAt(left) == s.charAt(right)) {
16 // Store the current matching character
17 char currentChar = s.charAt(left);
18
19 // Move left pointer to skip all consecutive occurrences of the matching character
20 while (left + 1 < right && s.charAt(left) == s.charAt(left + 1)) {
21 left++;
22 }
23
24 // Move right pointer to skip all consecutive occurrences of the matching character
25 while (left < right - 1 && s.charAt(right) == s.charAt(right - 1)) {
26 right--;
27 }
28
29 // Move both pointers inward to remove the matched prefix and suffix
30 left++;
31 right--;
32 }
33
34 // Calculate remaining length, ensuring non-negative result
35 // If pointers have crossed (right < left), return 0
36 return Math.max(0, right - left + 1);
37 }
38}
39
1class Solution {
2public:
3 int minimumLength(string s) {
4 // Initialize two pointers at the start and end of the string
5 int left = 0;
6 int right = s.size() - 1;
7
8 // Continue removing matching prefix and suffix while they exist
9 while (left < right && s[left] == s[right]) {
10 // Move left pointer to skip all consecutive identical characters
11 while (left + 1 < right && s[left] == s[left + 1]) {
12 ++left;
13 }
14
15 // Move right pointer to skip all consecutive identical characters
16 while (left < right - 1 && s[right] == s[right - 1]) {
17 --right;
18 }
19
20 // Move both pointers inward to remove the matching prefix and suffix
21 ++left;
22 --right;
23 }
24
25 // Calculate remaining length (ensure non-negative result)
26 return max(0, right - left + 1);
27 }
28};
29
1/**
2 * Finds the minimum length of string after removing matching prefix and suffix characters
3 * @param s - The input string to process
4 * @returns The minimum possible length after removal operations
5 */
6function minimumLength(s: string): number {
7 // Initialize two pointers at the start and end of the string
8 let leftPointer: number = 0;
9 let rightPointer: number = s.length - 1;
10
11 // Continue removing matching characters from both ends while they are equal
12 while (leftPointer < rightPointer && s[leftPointer] === s[rightPointer]) {
13 // Move left pointer to skip all consecutive identical characters
14 while (leftPointer + 1 < rightPointer && s[leftPointer + 1] === s[leftPointer]) {
15 leftPointer++;
16 }
17
18 // Move right pointer to skip all consecutive identical characters
19 while (leftPointer < rightPointer - 1 && s[rightPointer - 1] === s[rightPointer]) {
20 rightPointer--;
21 }
22
23 // Move both pointers inward after processing the matching group
24 leftPointer++;
25 rightPointer--;
26 }
27
28 // Calculate remaining length, ensuring it's non-negative
29 return Math.max(0, rightPointer - leftPointer + 1);
30}
31
Time and Space Complexity
The time complexity is O(n)
where n
is the length of the string s
. In the worst case, each character in the string is visited at most once. The outer while loop uses two pointers (i
and j
) that move towards each other from opposite ends of the string. The inner while loops skip over consecutive identical characters, but each character is still only processed once as the pointers never backtrack. Therefore, the total number of operations is linear with respect to the input size.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space. The variables i
and j
are integer pointers that store indices, requiring constant space regardless of the input string size. No additional data structures like arrays, lists, or recursive call stacks are used, so the memory usage remains constant.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Single Character Strings
A common mistake is not considering edge cases where the entire string consists of the same character (e.g., "aaaa"). In such cases, the algorithm should remove everything except potentially one character in the middle.
Pitfall Example:
# Incorrect approach that might stop early while left < right and s[left] == s[right]: left += 1 right -= 1 # This would leave "aa" from "aaaa" instead of "" or "a"
Solution: The correct implementation expands to include ALL consecutive identical characters before moving the pointers, ensuring complete removal of matching prefixes and suffixes.
2. Incorrect Pointer Boundary Checks
When expanding the pointers to include consecutive characters, developers often forget to check boundaries properly, leading to index out of bounds errors or incorrect results.
Pitfall Example:
# Dangerous: might cause index issues while s[left] == s[left + 1]: # Missing boundary check left += 1
Solution: Always include boundary checks:
while left + 1 < right and s[left] == s[left + 1]: left += 1
3. Forgetting to Store the Current Character
A subtle but critical mistake is comparing characters directly without storing the reference character, which can lead to incorrect comparisons after pointer movements.
Pitfall Example:
while left < right and s[left] == s[right]: # Expanding without storing the character while left + 1 < right and s[left] == s[left + 1]: left += 1 # After left moves, s[left] might be different! while left < right - 1 and s[right - 1] == s[right]: right -= 1
Solution: Store the matching character before expansion:
current_char = s[left] # Now use current_char for all comparisons during expansion
4. Off-by-One Errors in Final Length Calculation
The remaining string length calculation can be tricky when pointers cross or meet.
Pitfall Example:
return right - left # Misses the character at position 'right'
Solution: Use right - left + 1
for the correct count, and handle negative results:
return max(0, right - left + 1)
5. Not Understanding When to Stop
Some implementations continue trying to remove characters even when the prefix and suffix have different characters, or try to remove overlapping segments.
Pitfall Example:
# Wrong: trying to force removal even when characters don't match if left < right: left += 1 right -= 1
Solution: The main loop condition must check BOTH that pointers haven't crossed AND that characters match:
while left < right and s[left] == s[right]:
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!