Editorial
Core insight
Removing Stars From a String · Two Pointers
Core Insight for Removing Stars From a String
The core intuition that bridges the gap from the brute force to an optimal solution is realizing that character deletions do not require string reallocations.
By maintaining a write pointer, we establish an invariant: at any moment, the sequence of characters from index 0 to write - 1 represents the correctly processed string up to the current read position. When the read pointer encounters a normal character, we place it at the write pointer and advance write. When the read pointer encounters a *, we simply decrement the write pointer. Decrementing the write pointer logically "erases" the last valid character because the next normal character we encounter will overwrite it.
Visual Description:
Imagine a character array of length . Both pointers start at index 0. The read pointer advances one step to the right on every iteration. When read points to a letter (e.g., 'a'), that letter is copied into the array at the write index, and write moves one step right. When read points to a *, no character is copied; instead, the write pointer shifts one step left. The data at the old write position remains in memory temporarily, but it is effectively orphaned and will either be overwritten by future letters or ignored when we finalize the string length.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 2390: Removing Stars From a String Solution & Explanation
Problem Overview
TL;DR: The optimal solution uses a two-pointer read/write technique to process the string in a single pass, decrementing the write pointer whenever a star is encountered to simulate a backspace.
In LeetCode 2390, you are given a string containing lowercase English letters and star characters (*). Each star acts as a backspace command, requiring you to remove the star itself and the closest non-star character to its left. Your task is to process all operations and return the final modified string. Because the input guarantees the operation is always possible, you never have to worry about removing a character from an empty sequence. Solving Removing Stars From a String efficiently is a popular interview question because it tests a candidate's ability to optimize sequential data manipulation.
Brute Force Approach for Removing Stars From a String
The most intuitive but naive way to solve this problem is to repeatedly search for a star and remove it along with the character immediately preceding it.
In this approach, you would iterate through the string to find the first occurrence of a *. Once found, you slice the string to exclude the star and the character at the index right before it, concatenate the remaining left and right halves, and repeat the process from the beginning of the new string until no stars remain.
function removeStarsNaive(s):
while s contains '*':
index = find first index of '*' in s
s = s.substring(0, index - 1) + s.substring(index + 1)
return sTime Complexity Analysis & Failure Reason: This brute force approach operates in time complexity. Finding the star takes up to time, and creating a new string via slicing or concatenation also takes time. In the worst-case scenario (e.g., a string of characters followed by stars), this results in quadratic time complexity. Given the problem constraints where the string length can be up to , operations will result in a Time Limit Exceeded (TLE) error.
Algorithm Strategy: Two Pointer Patterns
To implement the optimal solution for interviews, we use a mutable sequence (like a character array or a mutable string, depending on the language) to avoid the overhead of immutable string concatenation.
- Create a character array
resultof the same length as the input strings. - Initialize an integer
writeto 0. This will act as our writing pointer. - Iterate a
readpointer from 0 to the length ofsminus 1. - For each character at
s[read]:- If the character is a
*, decrement thewritepointer by 1. - If the character is a lowercase letter, assign
result[write] = s[read]and increment thewritepointer by 1.
- If the character is a
- After the loop terminates, the valid characters are stored in
resultfrom index 0 towrite - 1. - Construct and return a new string using the prefix of
resultup to thewritepointer.
Execution Flow
Let's trace the algorithm using the string s = "leet**cod*e":
- Initialize
write = 0. read = 0('l'): Not a star.result[0] = 'l',writebecomes 1.read = 1('e'): Not a star.result[1] = 'e',writebecomes 2.read = 2('e'): Not a star.result[2] = 'e',writebecomes 3.read = 3('t'): Not a star.result[3] = 't',writebecomes 4. (Current valid prefix: "leet")read = 4('*'): Is a star. Decrementwriteto 3. (Logical prefix: "lee")read = 5('*'): Is a star. Decrementwriteto 2. (Logical prefix: "le")read = 6('c'): Not a star.result[2] = 'c',writebecomes 3. (Overwrites second 'e'. Prefix: "lec")read = 7('o'): Not a star.result[3] = 'o',writebecomes 4. (Overwrites 't'. Prefix: "leco")read = 8('d'): Not a star.result[4] = 'd',writebecomes 5.read = 9('*'): Is a star. Decrementwriteto 4. (Logical prefix: "leco")read = 10('e'): Not a star.result[4] = 'e',writebecomes 5. (Overwrites 'd'. Prefix: "lecoe")- Loop ends. Return substring from 0 to
write(length 5), which is"lecoe".
Proof of Correctness
The problem specifies that a star removes the closest non-star character to its left. In our sequential left-to-right traversal, the most recently written character represents the closest non-star character to the left of the current read position.
Decrementing the write pointer exactly drops this most recently written character from the valid output boundary. Because the problem constraints guarantee that the operation is always possible, we are mathematically assured that write will never drop below 0. Every valid character is kept, and every backspaced character is perfectly overwritten or excluded, strictly satisfying the rules of the problem.
Pattern Reuse Notes
The "Two Pointers - String Comparison with Backspaces" subpattern is highly reusable across string manipulation problems involving dynamic deletions. The following problems share the same pattern/subpattern:
- LeetCode 844: Backspace String Compare: Instead of returning the string, you use the exact same backspace logic (often traversing backwards to achieve space) to compare if two strings resolve to the same text.
- LeetCode 1598: Crawler Log Folder: The directory navigation
../acts identically to the*backspace character, moving yourwritepointer (or directory depth) backward while ignoring normal folder names.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 2390
In C++, strings are mutable, allowing us to perform the Two Pointer approach entirely in place for auxiliary space.
1#include <string>
2
3class Solution {
4public:
5 std::string removeStars(std::string s) {
6 int write = 0;
7
8 for (int read = 0; read < s.length(); ++read) {
9 if (s[read] == '*') {
10 write--; // Simulate backspace
11 } else {
12 s[write] = s[read]; // Overwrite and advance
13 write++;
14 }
15 }
16
17 // Return the valid prefix of the modified string
18 return s.substr(0, write);
19 }
20};Java Solution for LeetCode 2390
In Java, strings are immutable, so we must allocate a character array to act as our mutable buffer.
1class Solution {
2 public String removeStars(String s) {
3 char[] result = new char[s.length()];
4 int write = 0;
5
6 for (int read = 0; read < s.length(); read++) {
7 char c = s.charAt(read);
8 if (c == '*') {
9 write--; // Simulate backspace
10 } else {
11 result[write] = c; // Overwrite and advance
12 write++;
13 }
14 }
15
16 // Construct the final string from the valid boundaries
17 return new String(result, 0, write);
18 }
19}Python Solution for LeetCode 2390
To strictly adhere to the Two Pointer array-manipulation pattern in Python (avoiding dynamic stack operations), we pre-allocate a list of characters.
1class Solution:
2 def removeStars(self, s: str) -> str:
3 # Pre-allocate array to strictly use read/write pointers
4 result = [''] * len(s)
5 write = 0
6
7 for read in range(len(s)):
8 if s[read] == '*':
9 write -= 1 # Simulate backspace
10 else:
11 result[write] = s[read] # Overwrite and advance
12 write += 1
13
14 # Join only the valid prefix up to the write pointer
15 return "".join(result[:write])