2390. Removing Stars From a String
Problem Description
You are given a string s
that contains regular characters and asterisks (*
).
The task is to process the string by removing characters according to a specific rule involving the asterisks. For each asterisk in the string, you need to:
- Find the closest non-star character to its left
- Remove both that character and the asterisk itself
You need to apply this operation for every asterisk in the string and return the final result after all removals are complete.
For example, if you have the string "abc*de*"
:
- The first
*
removes thec
(closest non-star to its left), leaving"abde*"
- The second
*
removes thee
(closest non-star to its left), leaving"abd"
The problem guarantees that:
- There will always be enough non-star characters to the left of each asterisk (the operation is always possible)
- The final result is unique regardless of the order in which you process the asterisks
The solution uses a stack-based approach: traverse the string from left to right, push non-star characters onto a stack, and pop from the stack whenever you encounter an asterisk. This naturally handles the "closest to the left" requirement since the stack's top element is always the most recently added (rightmost) non-star character.
Intuition
When we see this problem, we need to think about how to efficiently track and remove the "closest non-star character to the left" of each asterisk.
The key insight is recognizing that "closest to the left" follows a Last-In-First-Out (LIFO) pattern. As we traverse the string from left to right, the most recently seen non-star character is exactly the one that should be removed when we encounter an asterisk.
This naturally leads us to think of using a stack data structure. Why? Because:
- As we move through the string, we're building up a collection of non-star characters that might potentially be removed later
- When we see an asterisk, we need to remove the most recent addition to this collection
- A stack perfectly models this behavior - we push elements as we see them, and pop the most recent one when needed
Let's trace through an example to see why this works. Consider "leet**cod*e"
:
- We push
'l'
,'e'
,'e'
,'t'
onto the stack:['l', 'e', 'e', 't']
- First
*
: pop't'
, stack becomes['l', 'e', 'e']
- Second
*
: pop'e'
, stack becomes['l', 'e']
- Push
'c'
,'o'
,'d'
: stack becomes['l', 'e', 'c', 'o', 'd']
- Third
*
: pop'd'
, stack becomes['l', 'e', 'c', 'o']
- Push
'e'
: final stack is['l', 'e', 'c', 'o', 'e']
The beauty of this approach is that we don't need to look backward or keep track of indices - the stack naturally maintains the order and availability of removable characters. Each operation is O(1)
, making the overall solution O(n)
where n
is the length of the string.
Learn more about Stack patterns.
Solution Approach
The implementation uses a stack simulation approach to solve this problem efficiently.
Algorithm Steps:
-
Initialize an empty list
ans
that will serve as our stack to store characters. -
Traverse the string character by character:
- If the current character
c
is an asterisk (*
):- Pop the top element from the stack using
ans.pop()
- This removes the closest non-star character to the left
- Pop the top element from the stack using
- If the current character is not an asterisk:
- Push it onto the stack using
ans.append(c)
- This character might be removed later if we encounter an asterisk
- Push it onto the stack using
- If the current character
-
Build the result string by joining all remaining characters in the stack using
''.join(ans)
Implementation Details:
class Solution:
def removeStars(self, s: str) -> str:
ans = [] # [Stack](/problems/stack_intro) to store characters
for c in s:
if c == '*':
ans.pop() # Remove the most recent non-star character
else:
ans.append(c) # Add non-star character to stack
return ''.join(ans) # Convert stack to final string
Why This Works:
- The stack (implemented as a list) maintains the order of non-star characters from left to right
- When we encounter an asterisk, the top of the stack is guaranteed to be the closest non-star character to its left
- The
pop()
operation removes this character inO(1)
time - After processing all characters, the stack contains exactly the characters that weren't removed
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of the input string, as we traverse the string once - Space Complexity:
O(n)
for the stack in the worst case when there are no asterisks
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with the string "ab*c*d"
to illustrate the solution approach.
Initial Setup:
- Input string:
"ab*c*d"
- Stack (ans):
[]
(empty)
Step-by-step Processing:
Step 1: Process character 'a'
- Current character is not an asterisk
- Push
'a'
onto the stack - Stack:
['a']
Step 2: Process character 'b'
- Current character is not an asterisk
- Push
'b'
onto the stack - Stack:
['a', 'b']
Step 3: Process character '*'
- Current character is an asterisk
- Pop the top element from stack (removes
'b'
) - Stack:
['a']
Step 4: Process character 'c'
- Current character is not an asterisk
- Push
'c'
onto the stack - Stack:
['a', 'c']
Step 5: Process character '*'
- Current character is an asterisk
- Pop the top element from stack (removes
'c'
) - Stack:
['a']
Step 6: Process character 'd'
- Current character is not an asterisk
- Push
'd'
onto the stack - Stack:
['a', 'd']
Final Result:
- Join the remaining characters in the stack:
''.join(['a', 'd'])
="ad"
- Output:
"ad"
This example demonstrates how the stack naturally handles the "closest to the left" requirement. When we encounter the first asterisk at position 2, it removes 'b'
(the most recent non-star character). When we encounter the second asterisk at position 4, it removes 'c'
(which had become the most recent non-star character after 'b'
was removed).
Solution Implementation
1class Solution:
2 def removeStars(self, s: str) -> str:
3 """
4 Remove stars and their preceding characters from a string.
5
6 Each '*' removes the closest non-star character to its left.
7
8 Args:
9 s: Input string containing letters and stars
10
11 Returns:
12 String after removing all stars and their corresponding characters
13 """
14 # Use a stack to track characters
15 result_stack = []
16
17 # Process each character in the string
18 for char in s:
19 if char == '*':
20 # Remove the most recent non-star character
21 result_stack.pop()
22 else:
23 # Add non-star character to the stack
24 result_stack.append(char)
25
26 # Convert the stack to a string and return
27 return ''.join(result_stack)
28
1class Solution {
2 public String removeStars(String s) {
3 // Use StringBuilder to efficiently build the result string
4 StringBuilder result = new StringBuilder();
5
6 // Iterate through each character in the input string
7 for (int i = 0; i < s.length(); i++) {
8 // Check if current character is a star
9 if (s.charAt(i) == '*') {
10 // Remove the last character from result (the star removes the previous character)
11 result.deleteCharAt(result.length() - 1);
12 } else {
13 // Append non-star character to the result
14 result.append(s.charAt(i));
15 }
16 }
17
18 // Convert StringBuilder to String and return
19 return result.toString();
20 }
21}
22
1class Solution {
2public:
3 string removeStars(string s) {
4 // Initialize result string to store characters after star removal
5 string result;
6
7 // Iterate through each character in the input string
8 for (char current_char : s) {
9 if (current_char == '*') {
10 // If current character is a star, remove the last character from result
11 result.pop_back();
12 } else {
13 // If current character is not a star, append it to result
14 result.push_back(current_char);
15 }
16 }
17
18 // Return the final string after all star operations
19 return result;
20 }
21};
22
1/**
2 * Removes stars and their preceding characters from a string
3 * Each star removes the closest non-star character to its left
4 * @param s - Input string containing letters and stars
5 * @returns Modified string after processing all stars
6 */
7function removeStars(s: string): string {
8 // Stack to store characters that haven't been removed
9 const resultStack: string[] = [];
10
11 // Iterate through each character in the input string
12 for (const character of s) {
13 if (character === '*') {
14 // Remove the most recent character when encountering a star
15 resultStack.pop();
16 } else {
17 // Add non-star characters to the stack
18 resultStack.push(character);
19 }
20 }
21
22 // Join all remaining characters to form the final string
23 return resultStack.join('');
24}
25
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. This is because we iterate through each character in the string exactly once, and both the append()
and pop()
operations on a list in Python have O(1)
amortized time complexity.
The space complexity is O(n)
. While the reference answer mentions O(1)
when ignoring the space consumption of the answer string, the actual space complexity must account for the ans
list which stores the result. In the worst case where the input string contains no stars, the ans
list will store all n
characters from the input string, requiring O(n)
space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Edge Cases with Multiple Consecutive Asterisks
A common mistake is assuming the algorithm might fail when multiple asterisks appear consecutively. Developers might worry that consecutive asterisks could cause issues or require special handling.
Why it's not actually a problem: The problem statement guarantees there will always be enough non-star characters to the left of each asterisk. This means if you have n
asterisks in a row, there must be at least n
non-star characters available to remove before encountering those asterisks.
Example: For string "abcd***e"
:
- First
*
removesd
→ stack:['a', 'b', 'c']
- Second
*
removesc
→ stack:['a', 'b']
- Third
*
removesb
→ stack:['a']
- Add
e
→ final result:"ae"
2. Attempting to Process Asterisks in Reverse or Using Complex Logic
Some developers might overthink the problem and try to:
- Process asterisks from right to left
- Count asterisks first and then remove characters
- Use additional data structures or complex indexing
Why the simple approach works: The stack-based approach naturally handles the "closest to the left" requirement. Processing left-to-right with immediate removal is both simpler and more efficient.
3. Using String Concatenation Instead of a List
A performance pitfall is building the result using string concatenation:
# Inefficient approach result = "" for c in s: if c == '*': result = result[:-1] # O(n) operation each time else: result += c # Creates new string object
Problem: Strings are immutable in Python, so each concatenation or slicing operation creates a new string object, leading to O(n²) time complexity.
Solution: Always use a list (as shown in the correct implementation) and join at the end. List operations like append()
and pop()
are O(1), making the overall algorithm O(n).
4. Forgetting to Handle Empty Stack Scenario (Though Guaranteed Not to Happen)
While the problem guarantees sufficient characters, in real-world scenarios or variations of this problem, you might need to handle the case where there's an asterisk but the stack is empty:
# Defensive programming approach for char in s: if char == '*': if result_stack: # Check if stack is not empty result_stack.pop() else: result_stack.append(char)
However, for this specific problem, this check is unnecessary due to the guarantee, and adding it might confuse the solution's clarity.
Which type of traversal does breadth first search do?
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!