Fancy string transformation visualization

1957: Delete Characters to Make Fancy String

Difficulty: Easy
Topics: Strings, Two Pointers
Companies: Google, Amazon, Microsoft
Pro Tip: For string modification problems, always consider using a two-pointer approach or building a new string through iteration to avoid O(n²) complexity.

Problem Statement

A fancy string is defined as a string where no three consecutive characters are identical. Given a string s, your task is to delete the minimum number of characters to make it fancy. Return the final string after deletion. The answer is guaranteed to be unique.

Example 1

Input: s = "leeetcode"

Output: "leetcode"

Explanation: Remove one 'e' from the first group of three 'e's

Example 2

Input: s = "aaabaaaa"

Output: "aabaa"

Explanation: Remove one 'a' from the first group and two 'a's from the second group

Problem Link: View on LeetCode ↗

Key Insight

The problem requires removing the minimum characters to ensure no three identical characters appear consecutively. The optimal solution is to:

  1. Traverse the string while building the result
  2. Keep at most two consecutive identical characters
  3. Skip any character that would form a third consecutive identical character
Important: The solution must be efficient for strings up to 105 characters and preserve the original character order.

Optimal Approach: Single Pass Filtering

Build the result string by iterating through each character while tracking consecutive counts.

Algorithm Steps

  1. Initialize an empty list/string builder for the result
  2. Iterate through each character in the input string
  3. For each character:
    • If result length ≥ 2 and last two characters are same as current: skip
    • Otherwise: append to result
  4. Return the built result

Complexity Analysis

Time Complexity Space Complexity
O(n) O(n)

Where n is the length of the string. We traverse the string once and store the result.

Optimal Solution
class Solution {
public:
    string makeFancyString(string s) {
        string result;
        for (char c : s) {
            int n = result.size();
            if (n >= 2 && result[n-1] == c && result[n-2] == c) {
                continue;
            }
            result.push_back(c);
        }
        return result;
    }
};
class Solution {
    public String makeFancyString(String s) {
        StringBuilder result = new StringBuilder();
        for (char c : s.toCharArray()) {
            int n = result.length();
            if (n >= 2 && 
                result.charAt(n-1) == c && 
                result.charAt(n-2) == c) {
                continue;
            }
            result.append(c);
        }
        return result.toString();
    }
}
class Solution:
    def makeFancyString(self, s: str) -> str:
        result = []
        for c in s:
            if len(result) >= 2 and result[-1] == c and result[-2] == c:
                continue
            result.append(c)
        return ''.join(result)
Implementation Note: This approach efficiently builds the result by checking only the last two characters, maintaining optimal O(n) performance.

Alternative Approach: Two Pointers

Modify the string in-place using two pointers for reduced space complexity.

Algorithm Steps

  1. Convert string to character array
  2. Initialize two pointers: writer (current position) and count (consecutive count)
  3. Iterate with reader pointer:
    • If same as previous: increment count
    • Else: reset count to 1
    • If count < 3: write character and advance writer
  4. Return substring up to writer index

Complexity Analysis

Time Complexity Space Complexity
O(n) O(1) extra space (in-place modification)
Two Pointers Solution
class Solution {
public:
    string makeFancyString(string s) {
        int writer = 0, count = 0;
        for (int reader = 0; reader < s.size(); ++reader) {
            if (reader > 0 && s[reader] == s[reader-1]) {
                count++;
            } else {
                count = 1;
            }
            if (count < 3) {
                s[writer++] = s[reader];
            }
        }
        return s.substr(0, writer);
    }
};
class Solution {
    public String makeFancyString(String s) {
        char[] chars = s.toCharArray();
        int writer = 0, count = 0;
        for (int reader = 0; reader < chars.length; reader++) {
            if (reader > 0 && chars[reader] == chars[reader-1]) {
                count++;
            } else {
                count = 1;
            }
            if (count < 3) {
                chars[writer++] = chars[reader];
            }
        }
        return new String(chars, 0, writer);
    }
}
class Solution:
    def makeFancyString(self, s: str) -> str:
        chars = list(s)
        writer = 0
        count = 0
        for reader in range(len(chars)):
            if reader > 0 and chars[reader] == chars[reader-1]:
                count += 1
            else:
                count = 1
            if count < 3:
                chars[writer] = chars[reader]
                writer += 1
        return ''.join(chars[:writer])
Implementation Note: This approach modifies the string in-place, reducing space complexity to O(1) extra space (excluding input storage).

Edge Cases and Testing

1. Short Strings (Length < 3)

Input: "a" → Output: "a"
Input: "aa" → Output: "aa"

2. No Consecutive Triplets

Input: "abc" → Output: "abc"

3. Multiple Consecutive Groups

Input: "aaaabbbcccddd" → Output: "aabbccdd"

4. All Identical Characters

Input: "aaaaa" → Output: "aa"

5. Mixed Characters

Input: "aabbccddeee" → Output: "aabbccddee"
Warning: Always test with maximum length input (105 characters) to ensure O(n) performance.

Frequently Asked Questions

1. Why is the result guaranteed to be unique?

Because we always keep the first two characters in any consecutive sequence and remove subsequent duplicates, maintaining the original order.

2. Can this be solved with regular expressions?

Yes, but regex would be inefficient for large inputs: re.sub(r'(.)\1{2,}', r'\1\1', s). Not suitable for n=105.

3. How does the two-pointer method save space?

It modifies the input character array in-place, using only O(1) extra space for pointers and counters.

4. What if the string has Unicode characters?

The algorithm works the same since it compares characters directly. Only storage would be affected.

5. Why not use a stack?

A stack would work but would use O(n) space without advantages over the simpler iterative approaches.

6. Can we achieve O(1) space without modifying input?

No, since strings are immutable in some languages. The two-pointer approach requires character array conversion.

7. How to handle empty strings?

The constraint ensures n ≥ 1, so no special handling is needed.

8. Is there a recursive solution?

Possible but impractical for large n due to stack overflow and O(n) space complexity.

9. How to extend this to k consecutive characters?

Change the constant 3 to k and adjust conditionals accordingly.

10. Why is this problem categorized as easy?

Because the optimal solution requires only a single pass with simple condition checks.

Final Insight: This problem demonstrates how careful iteration with state tracking can efficiently solve string modification tasks. The two-pointer approach optimizes space while maintaining clarity.