3170. Lexicographically Minimum String After Removing Stars
Problem Description
You are given a string s
. It may contain any number of '*'
characters. Your task is to remove all '*'
characters.
While there is a '*'
, perform the following operation:
- Delete the leftmost
'*'
and the smallest non-'*'
character to its left. If there are several smallest characters, you can delete any of them.
Return the lexicographically smallest resulting string after removing all '*'
characters.
Intuition
To solve this problem, we need to process each '*'
in the string and find the smallest possible non-'*'
character to remove each time. The smallest character is determined by lexicographical order, which is similar to alphabetical order.
The strategy is to use a data structure to keep track of indices of characters as we iterate through the string. For each '*'
, we determine the smallest non-'*'
character that appears before it. If multiple candidates exist, we prefer the one appearing most recently to ensure the lexicographic order is maintained.
We utilize a dictionary g
where each character maps to a list of its indices in the string. We also maintain a boolean array rem
to mark characters and '*'
for removal. As we iterate through the string:
- If a
'*'
is encountered, mark it for removal in therem
array. - Check all characters from 'a' to 'z'. For the first character list (in lexicographical order) that is not empty, remove the last index it contains, marking this index for removal.
- If a regular character is encountered, record its index.
Finally, create the result string by concatenating characters that are not marked for removal.
This ensures the remaining string is the lexicographically smallest possible after all operations are complete.
Learn more about Stack, Greedy and Heap (Priority Queue) patterns.
Solution Approach
The solution approach leverages two main data structures: a dictionary g
to track indices of each character in the string s
, and a boolean array rem
to track which characters need to be deleted.
Here’s a step-by-step breakdown of the algorithm:
-
Initialize Data Structures:
- Create a dictionary
g
to store lists of indices for each character ('a' to 'z') as they appear ins
. - Initialize a boolean array
rem
of lengthn
(wheren
is the length of the string) withFalse
, indicating initially that no characters are marked for removal.
- Create a dictionary
-
Traverse the String:
- For each character
c
ins
with indexi
:- If
c
is'*'
, perform the following:- Mark
rem[i]
asTrue
to indicate the'*'
should be removed. - Iterate over each character
a
from'a'
to'z'
. For the first character for whichg[a]
is not empty, pop the last element (which is the largest index for that character due to how indices are appended) fromg[a]
and mark this index for removal by settingrem[index]
toTrue
.
- Mark
- If
c
is not'*'
, append the indexi
to the listg[c]
.
- If
- For each character
-
Build the Result String:
- Construct the result string by concatenating characters from
s
for which the corresponding index inrem
isFalse
(i.e., not marked for removal).
- Construct the result string by concatenating characters from
This method ensures that as we encounter '*'
, we remove the smallest lexicographical character preceding it, maintaining an optimally ordered resulting string.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Consider the string s = "ab*cd*"
, and we wish to remove all the '*'
characters following the described approach.
Step 1: Initialize Data Structures
g
: A dictionary to map each charactera-z
to their indices in the string. Initially empty.rem
: A boolean array of length6
(same as the length ofs
), initialized to[False, False, False, False, False, False]
.
Step 2: Traverse the String
-
Index
0
: Charactera
, updateg
to{'a': [0], ...}
. -
Index
1
: Characterb
, updateg
to{'a': [0], 'b': [1], ...}
. -
Index
2
: Character*
, markrem
as[False, False, True, False, False, False]
.- Find the smallest character from
g
. 'a' and 'b' both have entries. Choose 'a' because it comes first lexicographically. - Remove 'a' from
g
, resulting ing = {'a': [], 'b': [1], ...}
. - Mark the index of 'a' (0) for removal:
rem
becomes[True, False, True, False, False, False]
.
- Find the smallest character from
-
Index
3
: Characterc
, updateg
to{'a': [], 'b': [1], 'c': [3], ...}
. -
Index
4
: Characterd
, updateg
to{'a': [], 'b': [1], 'c': [3], 'd': [4], ...}
. -
Index
5
: Character*
, markrem
as[True, False, True, False, False, True]
.- Find the smallest character from remaining entries in
g
. Here, 'b' is chosen as it is lexicographically smallest. - Remove 'b' from
g
, resulting ing = {'a': [], 'b': [], 'c': [3], 'd': [4], ...}
. - Mark the index of 'b' (1) for removal:
rem
becomes[True, True, True, False, False, True]
.
- Find the smallest character from remaining entries in
Step 3: Build the Result String
- Concatenate characters from
s
for indices that are not marked inrem
. Indices0
,1
,2
, and5
are marked for removal. - The resulting string is constructed from indices
3
and4
: "cd".
The lexicographically smallest resulting string after all operations is "cd"
.
Solution Implementation
1from collections import defaultdict
2from string import ascii_lowercase
3
4class Solution:
5 def clearStars(self, s: str) -> str:
6 # Dictionary to keep track of the indices of each alphabet character.
7 char_indices = defaultdict(list)
8 n = len(s) # Length of the input string.
9 remove = [False] * n # Boolean array to mark characters for removal.
10
11 for i, char in enumerate(s):
12 if char == "*":
13 # Mark '*' for removal.
14 remove[i] = True
15 # Attempt to find the most recent alphabet character to remove.
16 for alphabet in ascii_lowercase:
17 if char_indices[alphabet]:
18 # Mark the most recent occurrence for removal and break loop.
19 remove[char_indices[alphabet].pop()] = True
20 break
21 else:
22 # Record the index of the current alphabet character.
23 char_indices[char].append(i)
24
25 # Return the filtered string after removing marked characters.
26 return "".join(char for i, char in enumerate(s) if not remove[i])
27
1class Solution {
2 public String clearStars(String s) {
3 // Create an array of Deques to hold indices of each alphabet character
4 Deque<Integer>[] characterIndices = new Deque[26];
5 // Initialize each Deque in the array
6 Arrays.setAll(characterIndices, k -> new ArrayDeque<>());
7 int length = s.length();
8 // Array to mark characters that should be removed
9 boolean[] remove = new boolean[length];
10
11 // Iterate over the characters in the string
12 for (int i = 0; i < length; ++i) {
13 if (s.charAt(i) == '*') {
14 // Mark this star character for removal
15 remove[i] = true;
16 // Find the most recent character that is not a star and mark it for removal
17 for (int j = 0; j < 26; ++j) {
18 if (!characterIndices[j].isEmpty()) {
19 remove[characterIndices[j].pop()] = true;
20 break; // Only remove one most recent character
21 }
22 }
23 } else {
24 // Push the index of this character onto the respective Deque
25 characterIndices[s.charAt(i) - 'a'].push(i);
26 }
27 }
28
29 // Construct the resulting string excluding the marked characters
30 StringBuilder result = new StringBuilder();
31 for (int i = 0; i < length; ++i) {
32 if (!remove[i]) {
33 result.append(s.charAt(i));
34 }
35 }
36
37 return result.toString();
38 }
39}
40
1#include <string>
2#include <stack>
3#include <vector>
4
5class Solution {
6public:
7 string clearStars(string s) {
8 std::stack<int> charStack[26]; // Use 26 stacks for each lowercase alphabet character.
9 int n = s.length(); // Length of the input string.
10 std::vector<bool> toRemove(n); // Vector to indicate which characters to remove.
11
12 // Traverse the string and process each character.
13 for (int i = 0; i < n; ++i) {
14 if (s[i] == '*') { // If the current character is '*', mark for removal.
15 toRemove[i] = true;
16 // Find the first non-empty stack and remove the character index from it.
17 for (int j = 0; j < 26; ++j) {
18 if (!charStack[j].empty()) {
19 toRemove[charStack[j].top()] = true; // Mark the character at the top of the stack for removal.
20 charStack[j].pop(); // Remove the index from the stack.
21 break;
22 }
23 }
24 } else {
25 charStack[s[i] - 'a'].push(i); // Push the index of the character into the respective stack.
26 }
27 }
28
29 std::string result;
30 // Build the result string with characters that are not marked for removal.
31 for (int i = 0; i < n; ++i) {
32 if (!toRemove[i]) {
33 result.push_back(s[i]);
34 }
35 }
36 return result; // Return the final processed string.
37 }
38};
39
1function clearStars(s: string): string {
2 // Create an array of 26 empty arrays to store indices of each letter in the alphabet.
3 const indexGroups: number[][] = Array.from({ length: 26 }, () => []);
4 const length = s.length;
5 // Create an array to mark indices for removal.
6 const remove: boolean[] = Array(length).fill(false);
7
8 // Iterate through each character in the string.
9 for (let i = 0; i < length; ++i) {
10 if (s[i] === '*') {
11 // Mark the '*' character for removal.
12 remove[i] = true;
13 // Attempt to remove the most recent character that is not a '*'.
14 for (let j = 0; j < 26; ++j) {
15 if (indexGroups[j].length) {
16 // Pop the most recent index of the corresponding letter and mark it for removal.
17 remove[indexGroups[j].pop()!] = true;
18 break;
19 }
20 }
21 } else {
22 // Otherwise, record the index of the current character (non '*') in the corresponding group.
23 indexGroups[s.charCodeAt(i) - 97].push(i);
24 }
25 }
26
27 // Filter the string to remove all marked characters and return the cleaned string.
28 return s
29 .split('')
30 .filter((_, i) => !remove[i])
31 .join('');
32}
33
Time and Space Complexity
The time complexity of the code is O(n * |Σ|)
, where n
is the length of the string s
, and |Σ|
(Sigma) is the size of the character set, which is 26
in this problem. This comes from the outer loop iterating through each character of the string, and the inner loop iterating over the fixed character set (ascii_lowercase
), resulting in O(n * 26)
simplifiable to O(n)
in practice due to the constant factor.
The space complexity is O(n)
. This arises from the use of rem
and g
. The list rem
has n
elements corresponding to each character of the string. The defaultdict g
holds indices for each character that appears in the string, but its total length across all keys doesn't exceed n
, since it stores indices for each non-star character.
Learn more about how to find time and space complexity quickly.
Which data structure is used to implement recursion?
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
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!