1910. Remove All Occurrences of a Substring
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
ins
- Remove that occurrence from
s
- Repeat this process until
part
no longer appears ins
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
.
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:
-
While Loop Condition: We use
while part in s
to continuously check if the substringpart
exists anywhere in the strings
. Thein
operator in Python efficiently searches for the substring. -
String Replacement: Inside the loop, we use
s = s.replace(part, '', 1)
to perform the removal:replace(part, '', 1)
finds the first occurrence ofpart
ins
- 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
-
Termination: The loop continues until
part in s
returnsFalse
, meaning no more occurrences ofpart
exist in the string. -
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 EvaluatorExample 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 runsk
times (once for each occurrence ofpart
that needs to be removed) - In each iteration:
part in s
performs a substring search which takesO(n * m)
in the worst cases.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 requiresO(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.
Which of the following uses divide and conquer strategy?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!