Given a string s
, repeatedly remove all adjacent duplicate characters in the string until no more adjacent duplicates remain. Return the final string after all such removals have been performed.
s
are the same, both are removed.s
contains only lowercase English letters.
Example:
Input: s = "abbaca"
Output: "ca"
At first glance, the problem seems to suggest checking for duplicate letters and removing them. However, the key is that only adjacent duplicates are removed, and this process must be repeated because removing one pair can create new adjacent duplicates.
A brute-force approach might involve scanning the string for adjacent duplicates, removing them, and repeating the scan until no more duplicates are found. However, this would be inefficient, especially for long strings, as each scan could be O(n) and we might do it many times.
To optimize, we can use a stack data structure. As we iterate through the string, we compare each character with the top of the stack (the last character we processed). If they match, we pop the last character (removing both duplicates). If not, we push the character onto the stack. This way, we process each character only once, and the stack naturally handles the repeated removal process.
The optimized approach uses a stack to efficiently remove adjacent duplicates:
c
in the input string s
:
c
, pop the stack (remove the duplicate pair).c
onto the stack.Why this works: The stack always contains the current non-duplicate sequence. When a duplicate is found, both are removed. If removing a pair creates a new adjacent duplicate, the stack allows us to check and remove it in the same pass.
Let's walk through the sample input s = "abbaca"
:
[]
['a']
['a', 'b']
['a']
[]
['c']
['c', 'a']
Final stack: ['c', 'a']
Join to string: "ca"
Why this works: Each time we encounter a duplicate, both are removed instantly. The process is repeated naturally as the stack allows us to check for new adjacent duplicates after each removal.
class Solution:
def removeDuplicates(self, s: str) -> str:
stack = []
for c in s:
if stack and stack[-1] == c:
stack.pop()
else:
stack.append(c)
return ''.join(stack)
class Solution {
public:
string removeDuplicates(string s) {
string stack;
for (char c : s) {
if (!stack.empty() && stack.back() == c) {
stack.pop_back();
} else {
stack.push_back(c);
}
}
return stack;
}
};
class Solution {
public String removeDuplicates(String s) {
StringBuilder stack = new StringBuilder();
for (char c : s.toCharArray()) {
int len = stack.length();
if (len > 0 && stack.charAt(len - 1) == c) {
stack.deleteCharAt(len - 1);
} else {
stack.append(c);
}
}
return stack.toString();
}
}
var removeDuplicates = function(s) {
const stack = [];
for (let c of s) {
if (stack.length > 0 && stack[stack.length - 1] === c) {
stack.pop();
} else {
stack.push(c);
}
}
return stack.join('');
};
The stack approach is much more efficient and suitable for large strings.
The problem of removing all adjacent duplicates in a string can be solved efficiently with a stack-based approach. By processing each character once and using the stack to manage removal and re-checking for new duplicates, we achieve a clean O(n) solution. This method is both simple and powerful, demonstrating how the right data structure can transform a repeated-process problem into a single-pass algorithm.