[Easy] 1047. Remove All Adjacent Duplicates In String
You are given a string s consisting of lowercase English letters. A duplicate removal consists of choosing two adjacent and equal letters and removing them.
We repeatedly make duplicate removals on s until we no longer can.
Return the final string after all such duplicate removals have been made. It can be proven that the answer is unique.
Thinking Process
- Stack Pattern: This is essentially a stack problem - matching adjacent pairs
- Stack matches nested or LIFO structure (parentheses, monotonic scans).
- Push on open / larger; pop when the current element resolves pending work.
- Monotonic stack finds next greater/smaller in O(n).
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Monotonic stack (this problem) | O(n) | O(n) | Next greater/smaller element |
| Parentheses matching | O(n) | O(n) | Push open, pop on close |
| Expression evaluation | O(n) | O(n) | Operand + operator stacks |
| Stack simulation | O(n) | O(n) | Process in LIFO order |
Examples
Example 1:
Input: s = "abbaca"
Output: "ca"
Explanation:
For example, in "abbaca" we could remove "bb" since the letters are adjacent and equal, and this is the only possible move. The result of this move is "aaca" of which only "aa" is possible, so the final string is "ca".
Example 2:
Input: s = "azxxzy"
Output: "ay"
Explanation:
First, we remove "xx" to get "azzy". Then we remove "zz" to get "ay".
Constraints
1 <= s.length <= 10^5sconsists of lowercase English letters.
Solution Approaches
Approach 1: Stack-Based Solution
Time Complexity: O(n)
Space Complexity: O(n)
Use a stack (or string as stack) to track characters. When encountering a character that matches the top of the stack, pop it. Otherwise, push the character.
Approach 2: In-Place Two Pointers
Time Complexity: O(n)
Space Complexity: O(1) excluding output space
Use two pointers to simulate a stack in-place. left acts as the stack pointer, right iterates through the string.
How the Algorithms Work
Stack-Based Approach
Key Insight: This problem is similar to matching parentheses. When we see a duplicate, we remove both characters (like popping from stack).
Step-by-Step Example: s = "abbaca"
Step | Char | Stack Before | Action | Stack After
-----|------|--------------|--------|-------------
0 | - | "" | Init | ""
1 | 'a' | "" | Push | "a"
2 | 'b' | "a" | Push | "ab"
3 | 'b' | "ab" | Pop | "a" (b == b)
4 | 'a' | "a" | Pop | "" (a == a)
5 | 'c' | "" | Push | "c"
6 | 'a' | "c" | Push | "ca"
Result: "ca"
Visual Representation:
"abbaca"
↓
"a" → push 'a'
"ab" → push 'b'
"a" → pop 'b' (duplicate)
"" → pop 'a' (duplicate)
"c" → push 'c'
"ca" → push 'a'
In-Place Two Pointers Approach
Key Insight: Use left as a stack pointer. When we find a duplicate, decrement left (simulating pop). Otherwise, increment left and assign (simulating push).
Step-by-Step Example: s = "abbaca"
Step | right | s[right] | left | s[0..left] | Action
-----|-------|----------|------|-------------|--------
0 | 0 | 'a' | -1 | "" | left=0, s[0]='a'
1 | 1 | 'b' | 0 | "a" | left=1, s[1]='b'
2 | 2 | 'b' | 1 | "ab" | left=0 (pop: b==b)
3 | 3 | 'a' | 0 | "a" | left=-1 (pop: a==a)
4 | 4 | 'c' | -1 | "" | left=0, s[0]='c'
5 | 5 | 'a' | 0 | "c" | left=1, s[1]='a'
Result: s.substr(0, 2) = "ca"
Visual Representation:
Initial: left = -1, right = 0
s = ['a', 'b', 'b', 'a', 'c', 'a']
↑
right=0, left=-1 → left=0, s[0]='a'
s = ['a', 'b', 'b', 'a', 'c', 'a']
↑
right=1, left=0 → left=1, s[1]='b'
s = ['a', 'b', 'b', 'a', 'c', 'a']
↑
right=2, left=1, s[2]=='b'==s[1] → left=0 (pop)
s = ['a', 'b', 'b', 'a', 'c', 'a']
↑
right=3, left=0, s[3]=='a'==s[0] → left=-1 (pop)
s = ['a', 'b', 'b', 'a', 'c', 'a']
↑
right=4, left=-1 → left=0, s[0]='c'
s = ['a', 'b', 'b', 'a', 'c', 'a']
↑
right=5, left=0 → left=1, s[1]='a'
Final: s.substr(0, 2) = "ca"
Algorithm Breakdown
Stack-Based Solution
class Solution:
def removeDuplicates(self, s):
stk = []
for ch in s:
if stk and stk[-1] == ch:
stk.pop()
else:
stk.append(ch)
return "".join(stk)
How it works:
- Iterate through each character
- If stack is not empty and top matches current character → pop (remove duplicate)
- Otherwise → push current character
- Return final stack contents
In-Place Two Pointers Solution
class Solution:
def removeDuplicates(self, s):
left = -1
s = list(s)
for right in range(len(s)):
if left >= 0 and s[right] == s[left]:
left -= 1
continue
left += 1
s[left] = s[right]
return "".join(s[:left + 1])
How it works:
leftacts as stack pointer (points to last valid character)rightiterates through input string- If
left >= 0ands[right] == s[left]→ decrementleft(pop) - Otherwise → increment
leftand assigns[right](push) - Return substring from 0 to
left+1
Complexity
| Approach | Time | Space | Pros | Cons | |———-|——|——-|——|——| | Stack (String) | O(n) | O(n) | Simple, readable | Extra space | | In-Place Two Pointers | O(n) | O(1) | Space efficient | Slightly more complex |
Implementation Details
Stack-Based: Why String Works
str stk
for ch in s:
if not not stk and stk[-1] == ch:
stk.pop() # Remove duplicate
else :
stk.append(ch) # Add character
return stk
**Why use string instead of stack
- Easier to return result (no need to reverse)
- Same time complexity
- More memory efficient for this use case
In-Place: Two Pointer Logic
left = -1 # Stack pointer (points to last valid character)
for(right = 0 right < len(s) right += 1) :
if left >= 0 and s[right] == s[left]:
left -= 1 # Pop: move stack pointer back
continue
s[left += 1] = s[right] # Push: increment and assign
return s.substr(0, left + 1)
Why left = -1 initially?
- Represents empty stack
left >= 0check ensures stack is not empty before comparings[++left]increments first, then assigns (pushes to stack)
Why continue after decrementing?
- Skip assigning current character (we’ve already “popped” it)
- Move to next character immediately
Common Mistakes
- Empty string: Returns empty string
- All duplicates:
"aaaa"→"" - No duplicates:
"abc"→"abc" - Nested duplicates:
"abccba"→"" -
Single character:
"a"→"a" - Not handling empty stack: Forgetting to check
!stk.empty()before accessing top - Wrong comparison: Comparing with wrong character
- In-place index errors: Off-by-one errors with
leftpointer - Multiple passes: Trying to do multiple passes instead of single pass
- Not using continue: In in-place solution, forgetting
continueafter decrementing
Optimization Tips
- Use string as stack: More efficient than
stack<char>for this problem - In-place when possible: Two-pointer approach saves space
- Early termination: Can optimize if we know string length (not applicable here)
Related Problems
- 20. Valid Parentheses - Similar stack pattern
- 1544. Make The String Great - Similar duplicate removal
- 1209. Remove All Adjacent Duplicates in String II - Extension with k duplicates
- 1047. Remove All Adjacent Duplicates In String - This problem
Real-World Applications
- Text Processing: Removing duplicate characters in text editors
- Data Cleaning: Removing adjacent duplicates in data streams
- Compression: Basic run-length encoding preprocessing
- Parsing: Removing redundant tokens in parsers
Pattern Recognition
This problem demonstrates the “Stack for Matching Pairs” pattern:
1. Use stack to track elements
2. When encountering matching pair → pop
3. Otherwise → push
4. Return remaining stack contents
Similar problems:
- Valid Parentheses
- Make The String Great
- Remove K Digits
- Decode String
Why Stack Works
- LIFO Property: Last character added is first to be matched
- Adjacent Matching: Duplicates are always adjacent, matching stack’s top
- Cascading Removals: Removing one pair may create new adjacent pairs
- Single Pass: Stack handles cascading removals in one pass
In-Place Optimization Explanation
Why it works:
leftpointer simulates stack tops[0..left]represents current stack contents- When duplicate found, decrement
left(pop) - When new character, increment
leftand assign (push) - Final result is
s[0..left]
Space savings:
- Stack approach: O(n) extra space
- In-place: O(1) extra space (reusing input string)
Step-by-Step Trace: s = "azxxzy"
Stack Approach:
'a' → push → stack: "a"
'z' → push → stack: "az"
'x' → push → stack: "azx"
'x' → pop → stack: "az" (x == x)
'z' → pop → stack: "a" (z == z)
'y' → push → stack: "ay"
Result: "ay"
In-Place Approach:
right=0: 'a' → left=0, s[0]='a'
right=1: 'z' → left=1, s[1]='z'
right=2: 'x' → left=2, s[2]='x'
right=3: 'x' → left=1 (pop, x==x)
right=4: 'z' → left=0 (pop, z==z)
right=5: 'y' → left=1, s[1]='y'
Result: s.substr(0, 2) = "ay"
Key Takeaways
- Stack Pattern: This is essentially a stack problem - matching adjacent pairs
- In-Place Optimization: Can simulate stack using two pointers to save space
- Greedy Approach: Remove duplicates as soon as we find them
- No Need for Multiple Passes: Single pass is sufficient with proper data structure
References
- LC 1047: Remove All Adjacent Duplicates In String on LeetCode
- LeetCode Discuss — LC 1047: Remove All Adjacent Duplicates In String
- LeetCode Editorial (may require premium)