Given a string s
and an integer k
, remove all adjacent duplicates in the string where a group of exactly k
adjacent, identical characters are found. This process should be repeated as many times as possible until no more such groups exist. The final result should be returned as a string.
k
adjacent, identical characters are removed at each step.
Example:
Input: s = "deeedbbcccbdaa"
, k = 3
Output: "aa"
Explanation: First, remove "eee" and "ccc", then "bbb" becomes "bb", which is not enough, and finally, "aa" remains.
A naive approach would be to scan the string repeatedly, removing groups of k
identical adjacent characters until no more can be removed. However, this brute-force method is inefficient, especially for long strings and small k
.
To optimize, we need a way to efficiently track consecutive characters and their counts as we process the string. The key observation is that we only care about runs of adjacent, identical characters and their lengths. If we can track both the character and how many times it has appeared consecutively, we can decide when to remove a group in a single pass.
Using a stack is a natural fit: as we process each character, we push it onto the stack along with a count of how many times it has appeared consecutively. When the count reaches k
, we pop the group off the stack, effectively removing it from the string.
We use a stack to keep track of characters and their consecutive counts as we iterate through the string:
(character, count)
.k
, pop the top element off the stack (removing the group).(character, 1)
onto the stack.
This approach guarantees that we only need a single pass through the string and maintain at most O(n)
space for the stack, where n
is the length of the string.
Let's walk through the input s = "deeedbbcccbdaa"
with k = 3
:
Finally, reconstruct the string: 'aa'
.
O(n)
passes. In the worst case, this is O(n^2)
time.
O(n)
, where n
is the length of s
.
O(n)
characters and counts, so space complexity is O(n)
.
class Solution:
def removeDuplicates(self, s: str, k: int) -> str:
stack = [] # Each element is [char, count]
for char in s:
if stack and stack[-1][0] == char:
stack[-1][1] += 1
if stack[-1][1] == k:
stack.pop()
else:
stack.append([char, 1])
return ''.join(char * count for char, count in stack)
class Solution {
public:
string removeDuplicates(string s, int k) {
vector
class Solution {
public String removeDuplicates(String s, int k) {
Deque<Pair> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (!stack.isEmpty() && stack.peekLast().ch == c) {
stack.peekLast().count++;
if (stack.peekLast().count == k) {
stack.pollLast();
}
} else {
stack.addLast(new Pair(c, 1));
}
}
StringBuilder sb = new StringBuilder();
for (Pair p : stack) {
for (int i = 0; i < p.count; i++) sb.append(p.ch);
}
return sb.toString();
}
static class Pair {
char ch;
int count;
Pair(char ch, int count) {
this.ch = ch;
this.count = count;
}
}
}
var removeDuplicates = function(s, k) {
let stack = [];
for (let char of s) {
if (stack.length > 0 && stack[stack.length - 1][0] === char) {
stack[stack.length - 1][1]++;
if (stack[stack.length - 1][1] === k) {
stack.pop();
}
} else {
stack.push([char, 1]);
}
}
return stack.map(([char, count]) => char.repeat(count)).join('');
};
The Remove All Adjacent Duplicates in String II problem is efficiently solved using a stack to track both characters and their consecutive counts. This approach enables us to process the string in a single pass, removing groups of k
adjacent duplicates as soon as they form, and reconstructing the final string from the stack. The method is both time and space efficient, and the stack-based strategy elegantly captures the "remove as you go" requirement without repeated rescans or complex logic.