Minimum Length of String After Deleting Similar Ends
MedPrompt
Given a string s consisting only of characters 'a', 'b', and 'c'. You are asked to apply the following algorithm on the string any number of times:
- Pick a non-empty prefix from the string
swhere all the characters in the prefix are equal. - Pick a non-empty suffix from the string
swhere all the characters in this suffix are equal. - The prefix and the suffix should not intersect at any index.
- The characters from the prefix and suffix must be the same.
- Delete both the prefix and the suffix.
Return the minimum length of s after performing the above operation any number of times (possibly zero times).
Example 1:
Input: s = "ca"
Output: 2
Explanation: You can't remove any characters, so the string stays as is.Example 2:
Input: s = "cabaabac"
Output: 0
Explanation: An optimal sequence of operations is:
- Take prefix = "c" and suffix = "c" and remove them, s = "abaaba".
- Take prefix = "a" and suffix = "a" and remove them, s = "baab".
- Take prefix = "b" and suffix = "b" and remove them, s = "aa".
- Take prefix = "a" and suffix = "a" and remove them, s = "".Example 3:
Input: s = "aabccabba"
Output: 3
Explanation: An optimal sequence of operations is:
- Take prefix = "aa" and suffix = "a" and remove them, s = "bccabb".
- Take prefix = "b" and suffix = "bb" and remove them, s = "cca".
Constraints:
1 <= s.length <= 105sonly consists of characters'a','b', and'c'.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly simulates the process described in the problem. It repeatedly finds the prefix and suffix with identical characters, removes them by creating a new substring, and continues this process in a loop until no more characters can be removed. While intuitive, this method is inefficient because string manipulation, especially creating substrings in a loop, is computationally expensive.
Algorithm
- Start a loop that continues as long as the string
shas more than one character. - In the loop, check if the first and last characters of
sare the same. If not, break the loop. - If they are the same, identify the character
c. - Find the extent of the prefix of
c's by finding the first indexleftnot equal toc. - Find the extent of the suffix of
c's by finding the last indexrightnot equal toc. - If the entire string consists of
c(i.e.,leftpointer moves past therightpointer), the result is 0. - Otherwise, update
sby creating a new substring that excludes the identified prefix and suffix usings.substring(left, right + 1). - After the loop terminates, return the length of the final string
s.
Walkthrough
The algorithm works on the string s iteratively. In each iteration, it checks if the string has more than one character and if its first and last characters match. If they do, it identifies the common character c. It then finds the first index left that does not contain c from the beginning and the last index right that does not contain c from the end. If the left pointer crosses the right pointer, it implies the entire string consisted of the character c, and the resulting length is 0. Otherwise, the string s is updated to the substring between left and right. The loop continues until the ends don't match or the string becomes too short. Finally, the length of the resulting string s is returned.
public int minimumLength(String s) { while (s.length() > 1 && s.charAt(0) == s.charAt(s.length() - 1)) { char c = s.charAt(0); int left = 0; int right = s.length() - 1; while (left <= right && s.charAt(left) == c) { left++; } // This check is crucial for strings like "aaaa" if (left > right) { return 0; } while (right >= left && s.charAt(right) == c) { right--; } s = s.substring(left, right + 1); } return s.length();}Complexity
Time
O(N^2) in the worst case. For a string like "abacada...", each step removes only a few characters, and creating a substring can take O(N) time. With up to O(N) such steps, the total time is quadratic.
Space
O(N), where N is the length of the string. In each iteration, `substring` can create a new string object, potentially of length O(N).
Trade-offs
Pros
Simple to understand and implement as it directly models the problem statement.
Cons
Highly inefficient due to the overhead of creating new string objects in each iteration.
Worst-case time complexity is quadratic, which is too slow for large inputs.
Can lead to high memory consumption due to the creation of many temporary string objects.
A much more efficient approach uses two pointers, left and right, initialized to the start and end of the string, respectively. These pointers are moved inwards, skipping over the prefixes and suffixes that are to be deleted. This avoids the costly operation of creating new strings in each step, leading to an optimal linear time solution with constant space.
Algorithm
- Initialize two pointers:
leftat the start of the string (index 0) andrightat the end (s.length() - 1). - Use a
whileloop that continues as long asleft < right(to ensure prefix and suffix don't overlap) ands.charAt(left) == s.charAt(right). - Inside the loop, save the character
c = s.charAt(left). - Increment
leftin a nested loop whileleft <= rightands.charAt(left) == cto skip the entire prefix. - Decrement
rightin another nested loop whileright >= leftands.charAt(right) == cto skip the entire suffix. - Once the main loop terminates, the remaining part of the string is conceptually between indices
leftandright. - Return the length of this remaining part, which is calculated as
right - left + 1.
Walkthrough
This greedy algorithm uses two pointers, left starting at index 0 and right at s.length() - 1. The core of the algorithm is a while loop that runs as long as left is less than right and the characters at s[left] and s[right] are identical. This condition ensures we can perform an operation and that the prefix and suffix do not overlap.
Inside the loop:
- The common character
cat the ends is noted. - The
leftpointer is advanced forward past all consecutive occurrences ofc. - The
rightpointer is moved backward past all consecutive occurrences ofc.
This process effectively "deletes" the prefix and suffix without actually modifying the string or creating new ones. The loop terminates when the characters at left and right are different, or when left meets or surpasses right. The final length of the remaining string is the number of characters between the final left and right positions, inclusive. This is calculated as right - left + 1. If left has moved past right, this formula correctly yields 0 or a negative number, which corresponds to an empty remaining string.
public int minimumLength(String s) { int left = 0; int right = s.length() - 1; while (left < right && s.charAt(left) == s.charAt(right)) { char c = s.charAt(left); // Move left pointer to the right past all occurrences of c while (left <= right && s.charAt(left) == c) { left++; } // Move right pointer to the left past all occurrences of c while (right >= left && s.charAt(right) == c) { right--; } } // The remaining length is the distance between the two pointers return right - left + 1;}Complexity
Time
O(N), where N is the length of the string. Each pointer, `left` and `right`, traverses the string at most once in a single pass.
Space
O(1). Only a constant amount of extra space is used for the two pointers and a character variable, regardless of the input string size.
Trade-offs
Pros
Extremely efficient with linear time complexity.
Optimal space complexity, using only a constant amount of extra space.
Avoids expensive string creation and manipulation operations.
Cons
The logic with multiple nested loops and pointer updates might be slightly less intuitive to grasp initially compared to direct simulation.
Solutions
Solution
class Solution {public int minimumLength(String s) { int i = 0, j = s.length() - 1; while (i < j && s.charAt(i) == s.charAt(j)) { while (i + 1 < j && s.charAt(i) == s.charAt(i + 1)) { ++i; } while (i < j - 1 && s.charAt(j) == s.charAt(j - 1)) { --j; } ++i; --j; } return Math.max(0, j - i + 1); }}class Solution {public: int minimumLength(string s) { int i = 0, j = s.size() - 1; while (i < j && s[i] == s[j]) { while (i + 1 < j && s[i] == s[i + 1]) { ++i; } while (i < j - 1 && s[j] == s[j - 1]) { --j; } ++i; --j; } return max(0, j - i + 1); }};class Solution : def minimumLength ( self , s : str ) -> int : i , j = 0 , len ( s ) - 1 while i < j and s [ i ] == s [ j ]: while i + 1 < j and s [ i ] == s [ i + 1 ]: i += 1 while i < j - 1 and s [ j - 1 ] == s [ j ]: j -= 1 i , j = i + 1 , j - 1 return max ( 0 , j - i + 1 )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.