Delete Characters to Make Fancy String
EasyPrompt
A fancy string is a string where no three consecutive characters are equal.
Given a string s, delete the minimum possible number of characters from s to make it fancy.
Return the final string after the deletion. It can be shown that the answer will always be unique.
Example 1:
Input: s = "leeetcode"
Output: "leetcode"
Explanation:
Remove an 'e' from the first group of 'e's to create "leetcode".
No three consecutive characters are equal, so return "leetcode".Example 2:
Input: s = "aaabaaaa"
Output: "aabaa"
Explanation:
Remove an 'a' from the first group of 'a's to create "aabaaaa".
Remove two 'a's from the second group of 'a's to create "aabaa".
No three consecutive characters are equal, so return "aabaa".Example 3:
Input: s = "aab"
Output: "aab"
Explanation: No three consecutive characters are equal, so return "aab".
Constraints:
1 <= s.length <= 105sconsists only of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach uses a two-pointer technique to build the result string in a character array. It's a space-optimized variant of the StringBuilder approach, often with better performance due to direct array manipulation and avoiding StringBuilder overhead. This is a classic pattern for filtering an array or string.
Algorithm
- Handle the base case: if the string length is less than 3, return the string itself.
- Convert the input string
sinto a character arraychars. - Initialize a write pointer
j = 2. The first two characters are always kept. - Iterate with a read pointer
ifrom 2 tos.length() - 1. - Inside the loop, check if
chars[i]is different fromchars[j-1]orchars[j-2]. - If the condition is true, it means the character can be kept. Copy the character:
chars[j] = chars[i], and then incrementj. - After the loop, create a new string from the
charsarray, using the characters from index 0 up to (but not including)j. - Return the new string.
Walkthrough
This method simulates an in-place modification. We convert the input string to a character array. We use two pointers: a read pointer i that scans the original array, and a write pointer j that indicates the end of the valid 'fancy' string being built. The first two characters of the string are always part of the fancy string (if they exist), so we can initialize our result with them and start our pointers i and j from 2. We iterate with i from 2 to the end of the character array. For each character chars[i], we compare it with the last two characters of our valid prefix, which are at chars[j-1] and chars[j-2]. If chars[i] is not equal to both chars[j-1] and chars[j-2], it means we can keep this character. We place it at the j-th position (chars[j] = chars[i]) and advance the write pointer (j++). If chars[i] is the same as the previous two characters, we simply skip it by advancing i without changing j. After the loop, the fancy string is the prefix of the character array of length j. We create a new string from this prefix and return it.
class Solution { public String makeFancyString(String s) { int n = s.length(); if (n < 3) { return s; } char[] chars = s.toCharArray(); int j = 2; // j is the write pointer, points to the next available spot for (int i = 2; i < n; i++) { // i is the read pointer // We can keep the character if it's not the same as the previous two. if (chars[i] != chars[j - 1] || chars[i] != chars[j - 2]) { chars[j] = chars[i]; j++; } } return new String(chars, 0, j); }}Complexity
Time
O(N), where N is the length of the string `s`. We iterate through the character array once. Converting the string to a character array and creating the final string from the array both take O(N) time.
Space
O(N). We create a character array of size N to work with. The final returned string also takes O(N) space. While this is an 'in-place style' algorithm, in Java, it still requires O(N) space due to string immutability. The benefit is mainly in performance constant factors.
Trade-offs
Pros
Highly efficient, often faster in practice than
StringBuilderdue to direct memory access and avoiding object overhead.A common and useful pattern for array/string filtering problems.
Optimal time complexity.
Cons
The logic can be slightly more complex to reason about than the straightforward
StringBuilderapproach.
This approach iterates through the input string and builds a new result string. For each character, it decides whether to append it to the result by checking the last two characters of the currently built string. It uses standard string concatenation, which is inefficient in Java because strings are immutable.
Algorithm
- Initialize an empty string
result. - Iterate through the input string
sfrom left to right. - For each character
c, get the current length ofresult. - If
result.length()is less than 2, appendctoresultusing the+operator. - Otherwise, if
cis not equal to the last character ofresultorcis not equal to the second-to-last character ofresult, appendctoresult. - After iterating through all characters, return
result.
Walkthrough
We initialize an empty string, let's call it result. We loop through each character c of the input string s. In each iteration, we check the length of result. If the length is less than 2, we can safely append c. If the length is 2 or more, we check if the last character result.charAt(result.length() - 1) and the second to last character result.charAt(result.length() - 2) are both equal to the current character c. If they are not both equal to c, we append c to result using the + operator. This process continues until all characters in s are processed. The main drawback is that string concatenation result = result + c in a loop creates a new string object in each step, leading to poor performance.
class Solution { public String makeFancyString(String s) { if (s.length() < 3) { return s; } String result = ""; result += s.charAt(0); result += s.charAt(1); for (int i = 2; i < s.length(); i++) { char c = s.charAt(i); int len = result.length(); if (c != result.charAt(len - 1) || c != result.charAt(len - 2)) { result += c; } } return result; }}Complexity
Time
O(N^2), where N is the length of the string `s`. In Java, strings are immutable. The operation `result += c` creates a new `StringBuilder`, appends the characters, and then creates a new `String`. In a loop, this repeated creation and copying leads to quadratic time complexity.
Space
O(N), where N is the length of the string. Although many intermediate strings are created (leading to O(N^2) total allocations), the space required at any single point for the `result` string is at most O(N).
Trade-offs
Pros
Simple to understand and implement the logic.
Cons
Very inefficient due to repeated string concatenation in a loop.
Will likely result in a 'Time Limit Exceeded' error for large inputs as specified in the constraints.
A more efficient approach that uses a StringBuilder to construct the result string. StringBuilder is mutable, so appending characters is much faster than using string concatenation, leading to a linear time complexity.
Algorithm
- Initialize an empty
StringBuilder sb. - Iterate through the input string
sfrom left to right. - For each character
c, get the current length ofsb. - If
sb.length()is less than 2, appendctosb. - Otherwise, if
cis not equal tosb.charAt(sb.length() - 1)orcis not equal tosb.charAt(sb.length() - 2), appendctosb. - After the loop, convert
sbto a string and return it.
Walkthrough
This method follows the same logic as the brute-force approach but replaces the inefficient string concatenation with a StringBuilder. We initialize an empty StringBuilder. We iterate through the input string s. For each character, we check if it can be appended without creating three consecutive identical characters. The condition is the same: the StringBuilder's length is less than 2, or the new character is not identical to the last two characters already in the StringBuilder. If the condition holds, we append the character using StringBuilder.append(), which is an amortized O(1) operation. Finally, we convert the StringBuilder to a string and return it. This is the standard and recommended way to build strings in a loop in Java.
class Solution { public String makeFancyString(String s) { if (s.length() < 3) { return s; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); int len = sb.length(); if (len < 2 || c != sb.charAt(len - 1) || c != sb.charAt(len - 2)) { sb.append(c); } } return sb.toString(); }}Complexity
Time
O(N), where N is the length of the string `s`. We iterate through the string once, and each append operation on the `StringBuilder` takes amortized constant time.
Space
O(N), where N is the length of the string `s`. The `StringBuilder` can grow up to the size of the input string, requiring O(N) space for its internal character array.
Trade-offs
Pros
Efficient O(N) time complexity.
Idiomatic Java for string manipulation in loops.
Easy to read and maintain.
Cons
Uses O(N) extra space for the
StringBuilder.
Solutions
Solution
class Solution {public String makeFancyString(String s) { StringBuilder ans = new StringBuilder(); for (char c : s.toCharArray()) { int n = ans.length(); if (n > 1 && ans.charAt(n - 1) == c && ans.charAt(n - 2) == c) { continue; } ans.append(c); } return ans.toString(); }}function makeFancyString ( s ) { let [ n , ans ] = [ s . length , '' ]; for ( let i = 0 ; i < n ; i ++ ) { if ( s [ i ] !== s [ i - 1 ] || s [ i ] !== s [ i - 2 ]) { ans += s [ i ]; } } return ans ; }class Solution {public: string makeFancyString(string s) { string ans = ""; for (char &c : s) { int n = ans.size(); if (n > 1 && ans[n - 1] == c && ans[n - 2] == c) continue; ans.push_back(c); } return ans; }};class Solution: def makeFancyString(self, s: str) -> str: ans = [] for c in s: if len(ans) > 1 and ans[- 1] == ans[- 2] == c: continue ans . append(c) return '' . join(ans)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.