Skip to content
← All posts

Minimum Length of String After Deleting Similar Ends: Two-Pointer Trim

6 min read
leetcodeproblemmediumstringstwo-pointers

LeetCode 1750, Minimum Length of String After Deleting Similar Ends, asks you to repeatedly trim matching character groups from both ends of a string. You pick a non-empty prefix and a non-empty suffix where every character in both is the same letter, then delete them. The goal is to minimize the length of the remaining string after performing as many of these operations as you want.

This is a clean two-pointer problem. The trick is recognizing that you always want to trim greedily from the outside in.

a0a1b2c3c4a5b6b7a8leftright
s = "aabccabba". Both ends start with 'a' (green). The two pointers will trim matching character groups from both sides.

Why this problem matters

Two-pointer trimming from both ends is a pattern that shows up in many string and array problems. Valid Palindrome, for example, uses the same converging pointer structure. The difference here is that instead of comparing single characters, you are comparing character groups and consuming entire runs of matching characters at once.

What makes this problem interesting is the greedy insight. You never need to "save" a trimming operation for later. If both ends match, you should always trim them now. Skipping a valid trim cannot help you find a shorter result, because trimming only removes characters and never changes the characters that remain in the middle.

This problem also reinforces the idea that two pointers can do more than one step at a time. After finding that both ends share the same character, you advance each pointer past the entire run of that character. That inner skip loop is the same technique you see in problems like Remove Duplicates from Sorted Array and 3Sum.

The key insight

Place a left pointer at the start and a right pointer at the end. While left is still to the left of right and s[left] == s[right], you have a match. Record the matching character, then advance left forward past every occurrence of that character and advance right backward past every occurrence of that character. Once the characters at the two pointers differ, or the pointers cross, the remaining substring between left and right (inclusive) is the shortest you can achieve.

The reason this greedy approach works: each trim operation removes characters that are identical on both sides. Trimming now can never make a future trim impossible, because the characters in the middle are untouched. And not trimming when you could gains you nothing, since those matching outer characters will always be part of the string otherwise.

The solution

def minimumLength(s):
    left, right = 0, len(s) - 1

    while left < right and s[left] == s[right]:
        ch = s[left]
        while left <= right and s[left] == ch:
            left += 1
        while left <= right and s[right] == ch:
            right -= 1

    return right - left + 1

The outer loop checks whether the characters at both ends match. If they do, we store that character in ch and then skip past all occurrences of ch from both sides. The inner while loops use left <= right (not left < right) because it is possible for the pointers to meet and both still be pointing at the matching character. In that case, both pointers pass each other, and the result is zero: the entire string gets deleted.

After the loop, right - left + 1 gives the length of the undeleted portion. If left > right, this evaluates to a negative number or zero, but since left will be exactly right + 1 in the crossed case, the formula gives 0 correctly.

The inner skip loops use left <= right instead of left < right. This matters when the entire string consists of one repeated character, like "aaaa". With left < right, you would stop one character short and return 1 instead of 0.

Visual walkthrough

Step 1: left=0, right=8. s[0]='a' and s[8]='a' match.

a0a1b2c3c4a5b6b7a8leftright

Both ends are 'a'. We will delete all leading and trailing 'a's.

Step 2: Skip all 'a's from both sides. Deleted indices 0, 1, and 8.

a0a1b2c3c4a5b6b7a8leftright

Left skips two 'a's (indices 0, 1) and lands on 'b' at index 2. Right skips one 'a' (index 8) and lands on 'b' at index 7.

Step 3: left=2, right=7. s[2]='b' and s[7]='b' match.

a0a1b2c3c4a5b6b7a8leftright

Both pointers now point to 'b'. We will delete all leading and trailing 'b's from the remaining window.

Step 4: Skip all 'b's from both sides. Deleted indices 2, 7, and 6.

a0a1b2c3c4a5b6b7a8leftright

Left skips one 'b' (index 2) and lands on 'c' at index 3. Right skips two 'b's (indices 7, 6) and lands on 'a' at index 5.

Step 5: left=3, right=5. s[3]='c' != s[5]='a'. No match. Done!

a0a1b2c3c4a5b6b7a8leftright

Characters differ. The remaining string is "cca" (indices 3 to 5). Minimum length = 3.

The algorithm trims two rounds of matching groups. First, it removes the 'a' characters from both ends. Then it removes the 'b' characters from both ends. The remaining "cca" has 'c' on the left and 'a' on the right, so no further trimming is possible. The answer is 3.

Complexity analysis

ApproachTimeSpace
Two PointersO(n)O(1)

Time: O(n). Each character is visited at most once by each pointer. The left pointer only moves right, the right pointer only moves left, and together they cover at most n positions. Even with the nested inner loops, the total work is linear.

Space: O(1). Two integer pointers and one character variable. No auxiliary data structures.

The building blocks

1. Two-pointer convergence

Two pointers start at opposite ends and move inward based on a condition. This is the same skeleton used in Valid Palindrome, Container With Most Water, and the inner loop of 3Sum.

left, right = 0, len(s) - 1
while left < right and s[left] == s[right]:
    # process the match
    left += 1
    right -= 1

The convergence guarantees that the search space shrinks by at least one element per outer iteration. You never backtrack.

2. Character group skipping

Once you identify the matching character, you skip past all consecutive occurrences of it from both sides. This is a common sub-pattern in problems that deal with runs of identical values.

ch = s[left]
while left <= right and s[left] == ch:
    left += 1
while left <= right and s[right] == ch:
    right -= 1

This inner skip is what makes the algorithm trim entire groups rather than single characters. Without it, you would only remove one character from each side per iteration, which would still produce the correct result but would obscure the real structure of the problem.

Edge cases

  • Single character. "a" has left == right from the start. The outer loop condition left < right is false, so we return 1. You cannot delete a single character because you need both a non-empty prefix and a non-empty suffix.
  • All same characters. "aaaa" matches on the first check. Both inner loops consume all four characters, pointers cross, and the result is 0. The entire string is deleted.
  • No matching ends. "abcd" has s[0]='a' != s[3]='d'. The outer loop never executes, and we return the full length, 4.
  • Entire string deleted except middle. "aabaa" trims the 'a' groups from both sides. Left skips indices 0, 1. Right skips index 4. The remaining string is "b" at index 2, so the answer is 1.
  • Two distinct groups. "abba" trims 'a' from both ends, leaving "bb". Then 'b' matches on both sides, so both are trimmed. The answer is 0.

From understanding to recall

You have read through the logic and seen the walkthrough. The algorithm is short, just a while loop with two inner skip loops. But in a few weeks, the details fade: do the inner loops use left < right or left <= right? Do you store the matching character before or after advancing the pointer? These small choices determine correctness.

The two building blocks here, convergence and group skipping, are each small enough to drill independently. Once they are in your long-term memory, assembling the full solution becomes mechanical. You are not memorizing LeetCode 1750 specifically. You are internalizing two reusable techniques that appear across dozens of problems.

Related posts

Practice builds fluency. Reading builds familiarity. CodeBricks bridges the gap with spaced repetition that targets the building blocks, not just the final solution.