844. Backspace String Compare
844. Backspace String Compare
Problem Statement
Given two strings s and t, return true if they are equal when both are typed into empty text editors. '#' means a backspace character.
Note that after backspacing an empty text, the text will continue empty.
Examples
Example 1:
Input: s = "ab#c", t = "ad#c"
Output: true
Explanation: Both s and t become "ac".
Example 2:
Input: s = "ab##", t = "c#d#"
Output: true
Explanation: Both s and t become "".
Example 3:
Input: s = "a#c", t = "b"
Output: false
Explanation: s becomes "c" while t becomes "b".
Constraints
1 <= s.length, t.length <= 200sandtonly contain lowercase letters and'#'characters.
Clarification Questions
Before diving into the solution, here are 5 important clarifications and assumptions to discuss during an interview:
-
Backspace behavior: What does ‘#’ do? (Assumption: Backspace character - deletes the previous character in the string)
-
Comparison goal: What are we comparing? (Assumption: Compare final strings after processing all backspaces)
-
Return value: What should we return? (Assumption: Boolean - true if final strings are equal, false otherwise)
-
Empty string: What if backspace deletes all characters? (Assumption: Result is empty string “”)
-
Multiple backspaces: What if there are multiple consecutive ‘#’? (Assumption: Each ‘#’ deletes one character - can delete multiple characters)
Interview Deduction Process (10 minutes)
Step 1: Brute-Force Approach (2 minutes)
Initial Thought: “I need to compare strings after backspaces. Let me build final strings first.”
Naive Solution: Process each string to build final string after backspaces, then compare.
Complexity: O(n + m) time, O(n + m) space
Issues:
- Uses O(n + m) extra space
- Two passes needed
- Doesn’t leverage two-pointer technique
- Can be optimized
Step 2: Semi-Optimized Approach (3 minutes)
Insight: “I can process strings from right to left, skipping characters deleted by backspaces.”
Improved Solution: Process strings from right to left. When encountering ‘#’, skip next character. Compare characters as we go.
Complexity: O(n + m) time, O(1) space
Improvements:
- O(1) space - no extra strings needed
- Right-to-left processing handles backspaces naturally
- Single pass comparison
- Handles all cases correctly
Step 3: Optimized Solution (5 minutes)
Final Optimization: “Right-to-left two-pointer approach is optimal.”
Best Solution: Right-to-left two-pointer approach is optimal. Process both strings from end. When encountering ‘#’, skip characters. Compare characters directly.
Complexity: O(n + m) time, O(1) space
Key Realizations:
- Right-to-left processing is key insight
- O(n + m) time is optimal - must process each character
- O(1) space is optimal
- Two-pointer technique enables direct comparison
Solution Approach
This problem can be solved in two ways:
- Stack-based: Build the final strings and compare (O(n) time, O(n) space)
- Two Pointers (Backwards): Process strings backwards, skipping characters based on backspaces (O(n) time, O(1) space)
The two-pointer approach is more space-efficient and processes strings in reverse to handle backspaces naturally.
Key Insights:
- Backwards Processing: Process from right to left to handle backspaces naturally
- Skip Counter: Track how many characters to skip due to backspaces
- Character Matching: Compare actual characters (after handling backspaces) from both strings
- Early Termination: Return
falseif characters don’t match
Algorithm:
- Initialize: Two pointers at end of both strings, skip counters for both
- While either string has characters:
- Process backspaces in
s: skip#and characters to be deleted - Process backspaces in
t: skip#and characters to be deleted - Compare current characters: if they don’t match, return
false - Move both pointers left
- Process backspaces in
- Return:
trueif both pointers reached-1(both strings processed)
Solution
Solution: Two Pointers (Backwards)
class Solution {
public:
bool backspaceCompare(string s, string t) {
// 1. string construct: ab#c backwards construct: ca
// Request: O(n) time O(1) space -> in space update.
// 2. backwars, 2 pointers: if # move 2 backwards, then compare
int i = s.length() -1, j = t.length() - 1;
int skipS = 0, skipT = 0;
while(i >= 0 || j >= 0) {
while(i >= 0) {
if(s[i] == '#') {skipS++; i--;}
else if(skipS > 0) {skipS--; i--;}
else break;
}
while(j >=0) {
if(t[j] == '#') {skipT++; j--;}
else if(skipT > 0) {skipT--; j--;}
else break;
}
if(i >= 0 && j>= 0 && s[i] != t[j]) return false;
i--;
j--;
}
return i == j;
}
};
Algorithm Explanation:
- Initialize (Lines 6-7):
i = s.length() - 1,j = t.length() - 1: Start from end of both stringsskipS = 0,skipT = 0: Counters for characters to skip in each string
-
Main Loop (Line 8): Continue while either string has unprocessed characters
- Process Backspaces in
s(Lines 9-13):- If
s[i] == '#': IncrementskipSand move left (backspace found) - Else if
skipS > 0: DecrementskipSand move left (skip character due to backspace) - Else: Break (found actual character to compare)
- If
-
Process Backspaces in
t(Lines 14-18): Same logic for stringt - Compare Characters (Lines 19-20):
- If both pointers are valid (
i >= 0 && j >= 0) and characters don’t match, returnfalse - Move both pointers left
- If both pointers are valid (
- Return (Line 22):
i == jensures both strings are fully processed (both-1)
Why This Works:
- Backwards Processing: Processing from right to left handles backspaces naturally
- Skip Counter: Tracks how many characters to skip due to backspaces encountered
- Character Matching: Only compares actual characters (after handling backspaces)
- Space Efficient: O(1) space, no need to build final strings
Example Walkthrough:
For s = "ab#c", t = "ad#c":
Initial: i = 3, j = 3, skipS = 0, skipT = 0
Iteration 1:
Process s: i=3, s[3]='c', skipS=0 → break, i=3
Process t: j=3, t[3]='c', skipT=0 → break, j=3
Compare: s[3]='c' == t[3]='c' ✓
i=2, j=2
Iteration 2:
Process s: i=2, s[2]='#', skipS++ → skipS=1, i=1
i=1, s[1]='b', skipS>0 → skipS--, skipS=0, i=0
i=0, s[0]='a', skipS=0 → break, i=0
Process t: j=2, t[2]='#', skipT++ → skipT=1, j=1
j=1, t[1]='d', skipT>0 → skipT--, skipT=0, j=0
j=0, t[0]='a', skipT=0 → break, j=0
Compare: s[0]='a' == t[0]='a' ✓
i=-1, j=-1
Loop ends: i=-1, j=-1
Return: i == j → -1 == -1 → true
For s = "ab##", t = "c#d#":
Initial: i = 3, j = 3, skipS = 0, skipT = 0
Iteration 1:
Process s: i=3, s[3]='#', skipS++ → skipS=1, i=2
i=2, s[2]='#', skipS++ → skipS=2, i=1
i=1, s[1]='b', skipS>0 → skipS--, skipS=1, i=0
i=0, s[0]='a', skipS>0 → skipS--, skipS=0, i=-1
Process t: j=3, t[3]='#', skipT++ → skipT=1, j=2
j=2, t[2]='d', skipT>0 → skipT--, skipT=0, j=1
j=1, t[1]='#', skipT++ → skipT=1, j=0
j=0, t[0]='c', skipT>0 → skipT--, skipT=0, j=-1
Compare: i=-1, j=-1 (both exhausted)
i=-1, j=-1
Loop ends: i=-1, j=-1
Return: i == j → -1 == -1 → true
Complexity Analysis:
- Time Complexity: O(n + m) where n and m are lengths of
sandt- Each character is visited at most once
- Total: O(n + m)
- Space Complexity: O(1)
- Only using a few variables:
i,j,skipS,skipT - No additional data structures
- Only using a few variables:
Alternative Approach: Stack-Based
class Solution {
public:
bool backspaceCompare(string s, string t) {
return buildString(s) == buildString(t);
}
private:
string buildString(string& str) {
string result;
for(char c : str) {
if(c == '#') {
if(!result.empty()) {
result.pop_back();
}
} else {
result.push_back(c);
}
}
return result;
}
};
Time Complexity: O(n + m)
Space Complexity: O(n + m)
Comparison:
- Stack-based: Simpler to understand, but uses O(n + m) space
- Two Pointers: More complex, but O(1) space - better for large inputs
Key Insights
- Backwards Processing: Processing from right to left handles backspaces naturally
- Skip Counter: Tracks characters to skip due to backspaces
- Character Matching: Only compares actual characters after handling backspaces
- Space Efficiency: Two-pointer approach achieves O(1) space
Edge Cases
- All backspaces:
s = "###",t = "##"→ both become"", returntrue - Empty strings:
s = "",t = ""→ returntrue - Backspace at start:
s = "#a",t = "a"→ both become"a", returntrue - Different lengths:
s = "a#b",t = "b"→ both become"b", returntrue - No backspaces:
s = "abc",t = "abc"→ returntrue
Common Mistakes
- Wrong skip logic: Not properly handling consecutive backspaces
- Index errors: Off-by-one errors when moving pointers
- Missing break: Not breaking from inner loops when finding actual character
- Wrong comparison: Comparing before processing all backspaces
- Return condition: Not checking
i == jcorrectly (both should be-1)
Related Problems
- LC 1047: Remove All Adjacent Duplicates In String - Similar stack-based pattern
- LC 1209: Remove All Adjacent Duplicates in String II - K duplicates version
- LC 1544: Make The String Great - Similar character removal pattern
This problem demonstrates the two-pointer technique with backwards processing. The key insight is that processing from right to left makes handling backspaces straightforward, and using skip counters allows O(1) space complexity.