Apply Operations to Make String Empty
MedPrompt
You are given a string s.
Consider performing the following operation until s becomes empty:
- For every alphabet character from
'a'to'z', remove the first occurrence of that character ins(if it exists).
For example, let initially s = "aabcbbca". We do the following operations:
- Remove the underlined characters
s = "aabcbbca". The resulting string iss = "abbca". - Remove the underlined characters
s = "abbca". The resulting string iss = "ba". - Remove the underlined characters
s = "ba". The resulting string iss = "".
Return the value of the string s right before applying the last operation. In the example above, answer is "ba".
Example 1:
Input: s = "aabcbbca"
Output: "ba"
Explanation: Explained in the statement.Example 2:
Input: s = "abcd"
Output: "abcd"
Explanation: We do the following operation:
- Remove the underlined characters s = "abcd". The resulting string is s = "".
The string just before the last operation is "abcd".
Constraints:
1 <= s.length <= 5 * 105sconsists only of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly simulates the process described in the problem. It repeatedly applies the operation on the string until it becomes empty. In each step, it identifies the first occurrence of every unique character present in the string, removes them to form a new string, and then proceeds to the next step with this new string. The string from the step just before the string becomes empty is stored and returned as the result.
Algorithm
- Initialize an empty string
resultto store the string state before each operation. - Enter a loop that continues as long as the current string
sis not empty. - Inside the loop, first assign the current value of
storesult. - Create a
StringBuilderto build the string for the next iteration. - Use a
booleanarrayseenof size 26, initialized tofalse, to track which characters ('a' through 'z') have had their first occurrence removed in the current operation. - Iterate through each character
cof the current strings:- If
chas not beenseenyet (i.e.,seen[c - 'a']isfalse), mark it asseenand do not append it to theStringBuilder. This effectively removes its first occurrence. - If
chas already beenseen, append it to theStringBuilder.
- If
- After the iteration, update
sto the string constructed by theStringBuilder. - Once the loop terminates (when
sbecomes empty), return the value stored inresult.
Walkthrough
The algorithm maintains the string s and a variable result to store the state of s before an operation. It enters a while loop that continues as long as s is not empty. Inside the loop, the current s is saved to result. A boolean array seen of size 26 is used to track which characters have had their first occurrence removed in the current operation. A StringBuilder is used to construct the string for the next iteration. We iterate through the characters of the current string s. For each character c, if it's the first time we're seeing it in this iteration, we mark it as seen and skip it. Otherwise, we append it to the StringBuilder. After iterating through s, the StringBuilder contains the string for the next operation. We update s to this new string. The loop continues until s is empty. The final value stored in result is the answer.
class Solution { public String applyOperations(String s) { String result = ""; while (!s.isEmpty()) { result = s; StringBuilder nextS = new StringBuilder(); boolean[] seen = new boolean[26]; for (char c : s.toCharArray()) { int charIndex = c - 'a'; if (!seen[charIndex]) { seen[charIndex] = true; } else { nextS.append(c); } } s = nextS.toString(); } return result; }}Complexity
Time
O(N * K), where N is the initial length of the string and K is the number of operations. In the worst case (e.g., `s = "aaaaa..."`), K can be O(N), leading to a time complexity of O(N^2).
Space
O(N), where N is the length of the input string. This is because in each step, a new string (or `StringBuilder`) of a length up to N is created.
Trade-offs
Pros
Simple to understand and implement as it directly follows the problem description.
It is a good starting point for understanding the problem mechanics.
Cons
Highly inefficient for large inputs due to its quadratic time complexity in the worst case.
Repeatedly creates new string objects or manipulates a
StringBuilder, which can be slow and memory-intensive.Likely to result in a 'Time Limit Exceeded' error on platforms with strict time constraints for the given input size.
This optimal approach avoids the costly simulation by making a key observation about the process. The total number of operations is determined by the character with the highest frequency. The characters that are removed in the very last operation are precisely those that had this maximum frequency. Therefore, the problem reduces to finding these characters and arranging them in the order of their final appearance in the original string.
Algorithm
- Step 1: Count Frequencies and Last Indices.
- Create an integer array
freqof size 26 to store character counts and another integer arraylastIndexof size 26 to store the last seen index of each character. - Iterate through the input string
sonce from left to right. For each character, increment its count infreqand update its last seen index inlastIndex.
- Create an integer array
- Step 2: Find Maximum Frequency.
- Scan the
freqarray to find the maximum frequency,max_freq.
- Scan the
- Step 3: Collect Candidate Characters.
- Create a list to store candidate objects/pairs.
- Iterate from 'a' to 'z'. If a character's frequency in the
freqarray equalsmax_freq, add a new object containing this character and its last index (fromlastIndex) to the list.
- Step 4: Sort Candidates.
- Sort the list of candidates in ascending order based on their last indices.
- Step 5: Build and Return Result.
- Create a
StringBuilder. - Iterate through the sorted list of candidates and append each character to the
StringBuilder. - Return the final string from the
StringBuilder.
- Create a
Walkthrough
The core idea is to find all characters that appear most frequently and then arrange them based on their last occurrence in the original string. This can be done efficiently in a single pass.
First, we iterate through the input string s to populate two arrays of size 26: one for character frequencies (freq) and one for the last seen index of each character (lastIndex). After this single pass, we find the maximum frequency (max_freq) from the freq array. Then, we identify all characters that have a frequency equal to max_freq. For each of these characters, we retrieve their last known index. We collect these (character, last_index) pairs and sort them based on the index. This sorting step ensures that the characters are in the correct final order. Finally, we construct the result string by concatenating the characters from the sorted list.
import java.util.*; class Solution { // Helper class to store character and its last index private static class CharInfo { char character; int index; CharInfo(char character, int index) { this.character = character; this.index = index; } } public String applyOperations(String s) { int[] freq = new int[26]; int[] lastIndex = new int[26]; // Single pass to compute frequencies and last indices for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); freq[c - 'a']++; lastIndex[c - 'a'] = i; } // Find the maximum frequency int maxFreq = 0; for (int count : freq) { if (count > maxFreq) { maxFreq = count; } } // Collect characters with maximum frequency List<CharInfo> candidates = new ArrayList<>(); for (int i = 0; i < 26; i++) { if (freq[i] == maxFreq) { candidates.add(new CharInfo((char)('a' + i), lastIndex[i])); } } // Sort candidates by their last index Collections.sort(candidates, Comparator.comparingInt(a -> a.index)); // Build the result string StringBuilder result = new StringBuilder(); for (CharInfo ci : candidates) { result.append(ci.character); } return result.toString(); }}Complexity
Time
O(N), where N is the length of the string. The initial pass to calculate frequencies and last indices takes O(N). All subsequent steps (finding max frequency, collecting candidates, sorting, and building the string) take constant time because the alphabet size is fixed at 26.
Space
O(1). We use a few arrays of size 26 and a list that can hold at most 26 elements. The space required is constant and does not depend on the input string length N.
Trade-offs
Pros
Extremely efficient with a linear time complexity, making it suitable for large inputs.
Uses constant extra space, as the storage for frequencies, indices, and candidates does not depend on the input string's length.
Solves the problem with a single pass over the string, avoiding repeated and costly string manipulations.
Cons
Requires a logical leap to connect character frequencies to the final state, making it less intuitive than a direct simulation.
The implementation is slightly more complex, involving helper data structures to store and sort character information.
Solutions
Solution
class Solution {public String lastNonEmptyString(String s) { int[] cnt = new int[26]; int[] last = new int[26]; int n = s.length(); int mx = 0; for (int i = 0; i < n; ++i) { int c = s.charAt(i) - 'a'; mx = Math.max(mx, ++cnt[c]); last[c] = i; } StringBuilder ans = new StringBuilder(); for (int i = 0; i < n; ++i) { int c = s.charAt(i) - 'a'; if (cnt[c] == mx && last[c] == i) { ans.append(s.charAt(i)); } } return ans.toString(); }}class Solution {public: string lastNonEmptyString(string s) { int cnt[26]{}; int last[26]{}; int n = s.size(); int mx = 0; for (int i = 0; i < n; ++i) { int c = s[i] - 'a'; mx = max(mx, ++cnt[c]); last[c] = i; } string ans; for (int i = 0; i < n; ++i) { int c = s[i] - 'a'; if (cnt[c] == mx && last[c] == i) { ans.push_back(s[i]); } } return ans; }};class Solution: def lastNonEmptyString(self, s: str) -> str: cnt = Counter(s) mx = cnt . most_common(1)[0][1] last = {c: i for i, c in enumerate(s)} return "" . join(c for i, c in enumerate(s) if cnt[c] == mx and last[c] == i)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.