Facebook Pixel

1957. Delete Characters to Make Fancy String

EasyString
Leetcode Link

Problem Description

A fancy string is defined as a string where no three consecutive characters are the same.

Given a string s, you need to delete the minimum possible number of characters from s to make it a fancy string.

Return the final string after the deletion. The answer will always be unique.

For example:

  • If s = "aaabaaaa", you would need to remove some as to ensure no three consecutive as exist. The result would be "aabaa".
  • If s = "aab", the string is already fancy since there are no three consecutive equal characters, so return "aab".

The solution works by iterating through the string and keeping each character only if:

  • It's one of the first two characters (positions 0 or 1), OR
  • It's different from the character at position i-1, OR
  • It's different from the character at position i-2

This ensures that we never have three consecutive identical characters in the resulting string while removing the minimum number of characters necessary.

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 keep as many characters as possible while ensuring no three consecutive characters are the same. This means we should only remove a character when it would create a sequence of three identical consecutive characters.

Think about it this way: as we build our result string character by character, we need to check if adding the current character would violate the "fancy" property. The violation happens when the current character is the same as both of the two immediately preceding characters.

For any character at position i:

  • If i < 2, we don't have enough preceding characters to form a triplet, so we can safely include it
  • If the character is different from either s[i-1] or s[i-2], then even if we include it, we won't have three consecutive identical characters

This greedy approach works because we're making the locally optimal choice at each step - we include a character whenever possible and only skip it when absolutely necessary (when it would be the third consecutive identical character). Since we process the string from left to right and make decisions based only on the previous two characters, we ensure we remove the minimum number of characters needed to make the string fancy.

The condition c != s[i - 1] or c != s[i - 2] cleverly captures this logic - if the current character is different from either of the previous two, we're safe to include it. Only when c == s[i-1] == s[i-2] do we skip the character.

Solution Approach

We use a simulation approach to iterate through the string and build our answer character by character.

Data Structure Used:

  • An array ans to store the characters that will form our fancy string

Algorithm Steps:

  1. Initialize an empty list ans to collect the characters of our result string.

  2. Iterate through each character in the string s using enumeration to get both the index i and character c.

  3. For each character at position i, check if we should include it:

    • If i < 2: We're at the first or second position, so we can't have three consecutive characters yet. Include the character.
    • If c != s[i - 1]: The current character is different from the previous one, so it won't create three consecutive identical characters. Include it.
    • If c != s[i - 2]: The current character is different from the character two positions back, so even if it matches s[i-1], we won't have three consecutive identical characters. Include it.
  4. If any of the above conditions is true, append the character to ans.

  5. After processing all characters, join the characters in ans to form the final string using "".join(ans).

Why this works: The condition if i < 2 or c != s[i - 1] or c != s[i - 2] ensures that we only skip a character when i >= 2 AND c == s[i-1] AND c == s[i-2]. This is precisely when adding the current character would create three consecutive identical characters.

Time Complexity: O(n) where n is the length of the input string, as we iterate through the string once.

Space Complexity: O(n) for storing the result in the ans list.

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 walk through the solution with s = "aaabaaaa":

Initial Setup:

  • Input string: "aaabaaaa"
  • Result array ans = []

Step-by-step iteration:

  1. i=0, c='a':

    • Check: i < 2? Yes (0 < 2)
    • Action: Add 'a' to ans
    • ans = ['a']
  2. i=1, c='a':

    • Check: i < 2? Yes (1 < 2)
    • Action: Add 'a' to ans
    • ans = ['a', 'a']
  3. i=2, c='a':

    • Check: i < 2? No (2 ≮ 2)
    • Check: c != s[1]? No ('a' == 'a')
    • Check: c != s[0]? No ('a' == 'a')
    • Action: Skip this 'a' (would create "aaa")
    • ans = ['a', 'a']
  4. i=3, c='b':

    • Check: i < 2? No
    • Check: c != s[2]? Yes ('b' != 'a')
    • Action: Add 'b' to ans
    • ans = ['a', 'a', 'b']
  5. i=4, c='a':

    • Check: i < 2? No
    • Check: c != s[3]? Yes ('a' != 'b')
    • Action: Add 'a' to ans
    • ans = ['a', 'a', 'b', 'a']
  6. i=5, c='a':

    • Check: i < 2? No
    • Check: c != s[4]? No ('a' == 'a')
    • Check: c != s[3]? Yes ('a' != 'b')
    • Action: Add 'a' to ans
    • ans = ['a', 'a', 'b', 'a', 'a']
  7. i=6, c='a':

    • Check: i < 2? No
    • Check: c != s[5]? No ('a' == 'a')
    • Check: c != s[4]? No ('a' == 'a')
    • Action: Skip this 'a' (would create "aaa")
    • ans = ['a', 'a', 'b', 'a', 'a']
  8. i=7, c='a':

    • Check: i < 2? No
    • Check: c != s[6]? No ('a' == 'a')
    • Check: c != s[5]? No ('a' == 'a')
    • Action: Skip this 'a' (would create "aaa")
    • ans = ['a', 'a', 'b', 'a', 'a']

Final Result:

  • Join the array: "".join(ans) = "aabaa"
  • We removed 3 characters (at indices 2, 6, and 7) to prevent three consecutive 'a's

The algorithm successfully maintains the fancy string property by only skipping characters when they would form a triplet with the two preceding characters.

Solution Implementation

1class Solution:
2    def makeFancyString(self, s: str) -> str:
3        """
4        Remove characters to ensure no three consecutive characters are the same.
5      
6        Args:
7            s: Input string to process
8          
9        Returns:
10            A fancy string with no three consecutive identical characters
11        """
12        result = []
13      
14        # Iterate through each character with its index
15        for index, char in enumerate(s):
16            # Add character if:
17            # 1. It's one of the first two characters (index < 2), OR
18            # 2. It's different from the previous character, OR
19            # 3. It's different from the character two positions back
20            # This ensures we never have three consecutive identical characters
21            if index < 2 or char != s[index - 1] or char != s[index - 2]:
22                result.append(char)
23      
24        # Join the list of characters into a final string
25        return "".join(result)
26
1class Solution {
2    /**
3     * Removes consecutive characters that appear three or more times in a row,
4     * keeping at most two consecutive occurrences of any character.
5     * 
6     * @param s the input string to process
7     * @return a fancy string with no three consecutive identical characters
8     */
9    public String makeFancyString(String s) {
10        // Use StringBuilder for efficient string concatenation
11        StringBuilder result = new StringBuilder();
12      
13        // Iterate through each character in the input string
14        for (int i = 0; i < s.length(); i++) {
15            char currentChar = s.charAt(i);
16          
17            // Add the character if:
18            // 1. It's one of the first two characters (i < 2), OR
19            // 2. It's different from the previous character, OR
20            // 3. It's different from the character two positions back
21            // This ensures no three consecutive identical characters
22            if (i < 2 || currentChar != s.charAt(i - 1) || currentChar != s.charAt(i - 2)) {
23                result.append(currentChar);
24            }
25        }
26      
27        // Convert StringBuilder to String and return
28        return result.toString();
29    }
30}
31
1class Solution {
2public:
3    string makeFancyString(string s) {
4        string result = "";
5      
6        // Iterate through each character in the input string
7        for (int i = 0; i < s.length(); ++i) {
8            char currentChar = s[i];
9          
10            // Add the character if:
11            // 1. It's one of the first two characters (i < 2), OR
12            // 2. It doesn't form three consecutive identical characters
13            //    (current char is different from either of the two previous chars)
14            if (i < 2 || currentChar != s[i - 1] || currentChar != s[i - 2]) {
15                result += currentChar;
16            }
17        }
18      
19        return result;
20    }
21};
22
1/**
2 * Removes characters that appear three or more times consecutively in a string
3 * to make it "fancy" (no character appears 3+ times in a row)
4 * @param s - The input string to process
5 * @returns A fancy string with no character appearing 3+ times consecutively
6 */
7function makeFancyString(s: string): string {
8    // Array to build the result string character by character
9    const result: string[] = [];
10  
11    // Iterate through each character in the input string
12    for (let i = 0; i < s.length; i++) {
13        // Add current character if:
14        // 1. It's different from the previous character, OR
15        // 2. It's different from the character two positions back
16        // This ensures no three consecutive identical characters
17        if (s[i] !== s[i - 1] || s[i] !== s[i - 2]) {
18            result.push(s[i]);
19        }
20    }
21  
22    // Join the array of characters into a final string
23    return result.join('');
24}
25

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. The algorithm iterates through each character of the string exactly once using the enumerate function and for loop. Each operation inside the loop (checking conditions and appending to the list) takes O(1) time, resulting in a total time complexity of O(n).

The space complexity is O(n) for the output string stored in the ans list. However, if we exclude the space required for the output (which is necessary to return the result), the space complexity is O(1) as we only use a constant amount of extra space for the loop variables i and c.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrect Index Checking Logic

The Problem: A common mistake is trying to check characters at result[index - 1] and result[index - 2] instead of s[index - 1] and s[index - 2]. This leads to index out of bounds errors or incorrect comparisons.

Incorrect Implementation:

def makeFancyString(self, s: str) -> str:
    result = []
    for index, char in enumerate(s):
        # WRONG: Checking result array instead of original string
        if len(result) < 2 or char != result[-1] or char != result[-2]:
            result.append(char)
    return "".join(result)

Why It's Wrong: The indices in result don't correspond to the indices in s. When we skip characters, the result array becomes shorter than the original string, making index-based comparisons incorrect.

Correct Approach: Always compare against the original string s using the current index, not against the result array.

Pitfall 2: Using String Concatenation Instead of List

The Problem: Building the result using string concatenation (result += char) instead of a list leads to poor performance.

Inefficient Implementation:

def makeFancyString(self, s: str) -> str:
    result = ""  # Using string instead of list
    for index, char in enumerate(s):
        if index < 2 or char != s[index - 1] or char != s[index - 2]:
            result += char  # String concatenation - O(n) operation
    return result

Why It's Inefficient: Strings are immutable in Python. Each concatenation creates a new string object, copying all previous characters. This turns the overall complexity from O(n) to O(n²).

Solution: Use a list to collect characters and join them at the end. List append operations are O(1) amortized, keeping the overall complexity at O(n).

Pitfall 3: Misunderstanding the Condition Logic

The Problem: Using AND instead of OR in the condition, or negating the logic incorrectly.

Incorrect Implementation:

def makeFancyString(self, s: str) -> str:
    result = []
    for index, char in enumerate(s):
        # WRONG: Using AND instead of OR
        if index < 2 and char != s[index - 1] and char != s[index - 2]:
            result.append(char)
    return "".join(result)

Why It Fails: This would only include characters at positions 0 and 1 if they're different from non-existent previous positions, causing errors or unexpected behavior.

Correct Logic: We want to include a character if ANY of these conditions is true:

  • It's at position 0 or 1 (can't form three consecutive yet)
  • It differs from the immediate previous character
  • It differs from the character two positions back

The OR operator ensures we only exclude a character when ALL conditions for inclusion are false (i.e., when we have three consecutive identical characters).

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More