Facebook Pixel

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:

  1. Choose a non-empty prefix of s where all characters in the prefix are the same (e.g., "aaa" from "aaabccc")
  2. Choose a non-empty suffix of s where all characters in the suffix are the same (e.g., "ccc" from "aaabccc")
  3. The prefix and suffix must not overlap (they cannot share any index position)
  4. 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.)
  5. 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.

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

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:

  1. The characters at the two pointers are different (no more valid operations possible)
  2. 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:

  1. Initialize two pointers: Set i = 0 pointing to the start of the string and j = len(s) - 1 pointing to the end of the string.

  2. Main loop condition: Continue the process while i < j and s[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
  3. 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 to s[i]. The condition i + 1 < j ensures we don't cross the right pointer.

  4. 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 to s[j]. The condition i < j - 1 prevents crossing.

  5. 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
  6. Calculate the result: After the loop ends (when characters at i and j are different or pointers have crossed), the remaining string length is j - i + 1. We use max(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 Evaluator

Example 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' and s[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' β†’ Move i to position 1
    • s[1] = 'a', s[2] = 'b' β†’ Stop expanding (different character)
  • Expand right pointer to include all consecutive 'a's:
    • s[4] = 'a', s[3] = 'a' β†’ Move j to position 3
    • s[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 and j = 2 (pointers meet)
  • Since i is not less than j, 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]:
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More