Facebook Pixel

1910. Remove All Occurrences of a Substring

MediumStackStringSimulation
Leetcode Link

Problem Description

You are given two strings s and part. Your task is to repeatedly remove the leftmost occurrence of the substring part from s until no more occurrences of part exist in s.

The removal process works as follows:

  • Find the leftmost (first) occurrence of part in s
  • Remove that occurrence from s
  • Repeat this process until part no longer appears in s

Return the final string after all occurrences of part have been removed.

For example, if s = "daabcbaabcbc" and part = "abc", the process would be:

  • First removal: "daabcbaabcbc""dabaabcbc" (remove leftmost "abc")
  • Second removal: "dabaabcbc""dababc" (remove leftmost "abc")
  • Third removal: "dababc""dab" (remove leftmost "abc")
  • No more "abc" exists, so return "dab"

The solution uses a while loop that continues as long as part exists in s. In each iteration, it uses s.replace(part, '', 1) to replace only the first (leftmost) occurrence of part with an empty string, effectively removing it. The process continues until part is no longer found in s.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to keep removing occurrences of part from the string, but always starting from the leftmost position. This is important because removing one occurrence might create a new occurrence that wasn't there before.

Consider why we can't just remove all occurrences at once: if we have s = "ababcc" and part = "abc", after removing the first "abc", we get "abc" which itself contains another "abc". This cascading effect means we need to repeatedly check and remove until no more matches exist.

The natural approach is to use a loop that continues while part still exists in s. Python's in operator makes it easy to check if part is still present. When we find it, we need to remove only the leftmost occurrence (not all occurrences at once), which is why we use replace(part, '', 1) where the 1 parameter ensures only the first match is replaced.

This greedy approach of always removing the leftmost occurrence first guarantees we'll eventually remove all possible occurrences, including those that might be formed after previous removals. The process naturally terminates when no substring matching part can be found in s.

Learn more about Stack patterns.

Solution Approach

The implementation uses a straightforward iterative approach with string manipulation:

  1. While Loop Condition: We use while part in s to continuously check if the substring part exists anywhere in the string s. The in operator in Python efficiently searches for the substring.

  2. String Replacement: Inside the loop, we use s = s.replace(part, '', 1) to perform the removal:

    • replace(part, '', 1) finds the first occurrence of part in s
    • The empty string '' as the second argument means we're removing (replacing with nothing)
    • The third argument 1 limits the replacement to only the first occurrence (leftmost)
    • The result is assigned back to s, updating it for the next iteration
  3. Termination: The loop continues until part in s returns False, meaning no more occurrences of part exist in the string.

  4. Return Value: Once the loop exits, we return the modified string s.

The algorithm's simplicity comes from leveraging Python's built-in string methods:

  • Time complexity depends on the number of occurrences that need to be removed and the string operations
  • Each replace operation takes O(n) time where n is the length of the string
  • In the worst case, we might need to perform multiple iterations, each scanning and modifying the string

This approach doesn't require additional data structures - it modifies the string in-place (creating new string objects due to Python's immutable strings) and uses only the original string variable to track the current state.

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 a small example where s = "aabababa" and part = "aba".

Initial state: s = "aabababa", part = "aba"

Step 1: Check if "aba" exists in "aabababa"

  • Yes, it exists at position 1
  • Find leftmost occurrence: "aabababa"
  • Remove it: s = "a" + "" + "baba" = "ababa"

Step 2: Check if "aba" exists in "ababa"

  • Yes, it exists at position 0
  • Find leftmost occurrence: "ababa"
  • Remove it: s = "" + "" + "ba" = "ba"

Step 3: Check if "aba" exists in "ba"

  • No, "aba" is not found in "ba"
  • Exit the loop

Final result: "ba"

Notice how removing the first "aba" in step 1 created a new "aba" pattern that wasn't initially visible. This demonstrates why we need to repeatedly check and remove, rather than trying to remove all occurrences at once. The algorithm correctly handles these cascading removals by always targeting the leftmost occurrence in each iteration.

Solution Implementation

1class Solution:
2    def removeOccurrences(self, s: str, part: str) -> str:
3        """
4        Remove all occurrences of substring 'part' from string 's'.
5        Continues removing until no more occurrences exist.
6      
7        Args:
8            s: The input string to process
9            part: The substring to be removed
10          
11        Returns:
12            The resulting string after all removals
13        """
14        # Keep removing the first occurrence of 'part' while it exists in 's'
15        while part in s:
16            # Replace only the first occurrence of 'part' with empty string
17            s = s.replace(part, '', 1)
18      
19        # Return the final string after all occurrences are removed
20        return s
21
1class Solution {
2    /**
3     * Removes all occurrences of a substring from a string.
4     * Repeatedly finds and removes the first occurrence of 'part' from 's'
5     * until no more occurrences exist.
6     * 
7     * @param s    the original string to process
8     * @param part the substring to be removed from the original string
9     * @return     the modified string after all occurrences of 'part' are removed
10     */
11    public String removeOccurrences(String s, String part) {
12        // Continue removing while the part substring exists in s
13        while (s.contains(part)) {
14            // Replace the first occurrence of part with an empty string
15            s = s.replaceFirst(part, "");
16        }
17      
18        // Return the final string after all removals
19        return s;
20    }
21}
22
1class Solution {
2public:
3    string removeOccurrences(string s, string part) {
4        // Get the length of the substring to be removed
5        int partLength = part.size();
6      
7        // Continue removing occurrences while the substring exists in the string
8        while (s.find(part) != string::npos) {
9            // Find the position of the first occurrence of 'part'
10            size_t position = s.find(part);
11          
12            // Remove the substring starting at 'position' with length 'partLength'
13            s.erase(position, partLength);
14        }
15      
16        // Return the modified string after all occurrences are removed
17        return s;
18    }
19};
20
1/**
2 * Removes all occurrences of a substring from a string
3 * Repeatedly removes the first occurrence of 'part' from 's' until no more occurrences exist
4 * @param s - The input string to process
5 * @param part - The substring to remove from the input string
6 * @returns The resulting string after all removals
7 */
8function removeOccurrences(s: string, part: string): string {
9    // Continue removing while the part substring exists in the string
10    while (s.includes(part)) {
11        // Replace the first occurrence of part with empty string
12        s = s.replace(part, '');
13    }
14  
15    // Return the final string after all occurrences are removed
16    return s;
17}
18

Time and Space Complexity

Time Complexity: O(n * m * k) where n is the length of string s, m is the length of string part, and k is the number of occurrences of part in s.

  • The while loop runs k times (once for each occurrence of part that needs to be removed)
  • In each iteration:
    • part in s performs a substring search which takes O(n * m) in the worst case
    • s.replace(part, '', 1) also performs a substring search to find the first occurrence (O(n * m)) and then creates a new string with the replacement (O(n))
  • Therefore, each iteration takes O(n * m) time
  • Total time complexity: O(k * n * m)

In the worst case where removing one occurrence of part exposes another occurrence, k could be O(n/m), making the worst-case time complexity O(n²).

Space Complexity: O(n)

  • The replace method creates a new string in each iteration, which requires O(n) space
  • Python strings are immutable, so each modification creates a new string object
  • However, only one string is maintained at a time (the previous version is garbage collected)
  • Therefore, the space complexity is O(n) for storing the modified string

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

Common Pitfalls

1. Inefficient String Operations in Languages with Immutable Strings

In Python, strings are immutable, meaning each replace() operation creates a new string object. For very long strings with many occurrences to remove, this can lead to performance issues due to repeated string creation and copying.

Solution: For better performance with large inputs, consider using a list-based approach or StringBuilder-like pattern:

def removeOccurrences(self, s: str, part: str) -> str:
    result = []
    i = 0
    while i < len(s):
        # Check if 'part' starts at current position
        if s[i:i+len(part)] == part:
            # Try to match with previously added characters
            if len(result) >= len(part) - 1:
                # Check if combining result tail with new chars forms 'part'
                temp = ''.join(result[-(len(part)-1):]) + s[i]
                if part in temp:
                    # Remove the matching portion from result
                    for _ in range(len(part) - 1):
                        result.pop()
                    i += 1
                    continue
        result.append(s[i])
        i += 1
  
    # Final check for 'part' in result
    final = ''.join(result)
    while part in final:
        final = final.replace(part, '', 1)
    return final

2. Not Handling Overlapping Patterns After Removal

The current solution correctly handles this, but a common mistake is not recognizing that removing one occurrence can create a new occurrence. For example, with s = "ababcc" and part = "abc", after removing the first "abc", we get "abc" again.

Why the current solution works: The while loop continues checking until no occurrences remain, naturally handling newly formed patterns.

3. Stack-Based Alternative Approach

A more efficient approach uses a stack to build the result character by character, checking for pattern matches:

def removeOccurrences(self, s: str, part: str) -> str:
    stack = []
    part_len = len(part)
  
    for char in s:
        stack.append(char)
        # Check if the last part_len characters form 'part'
        if len(stack) >= part_len:
            if ''.join(stack[-part_len:]) == part:
                # Remove the last part_len characters
                for _ in range(part_len):
                    stack.pop()
  
    return ''.join(stack)

This approach has O(n*m) time complexity where n is the length of s and m is the length of part, but avoids creating multiple intermediate strings.

4. Edge Case: Empty Part String

If part is an empty string, the in operator will always return True, causing an infinite loop.

Solution: Add a guard clause:

if not part:
    return s

5. Memory Considerations for Very Long Strings

The repeated string creation can cause memory pressure. In production systems with memory constraints, consider streaming or chunked processing approaches for extremely large inputs.

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

Which of the following uses divide and conquer strategy?


Recommended Readings

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

Load More