A clear explanation of the Backspace String Compare problem using stack simulation and an O(1) space two-pointer scan.
Problem Restatement
We are given two strings s and t.
Each string contains lowercase letters and the character "#".
The "#" character acts like a backspace in a text editor. It deletes the character immediately before it. If there is no character before it, it does nothing.
Return True if both strings become equal after applying all backspaces. Otherwise, return False.
Input and Output
| Item | Meaning |
|---|---|
| Input | Two strings s and t |
| Output | True if final typed strings are equal |
| Backspace | "#" deletes the previous typed character |
| Empty text | Backspace on empty text has no effect |
| Follow-up | Solve in O(n) time and O(1) space |
Function shape:
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
...Examples
Example 1:
s = "ab#c"
t = "ad#c"Process s:
"ab#c" -> "ac"Process t:
"ad#c" -> "ac"Both final strings are equal.
So the answer is:
TrueExample 2:
s = "ab##"
t = "c#d#"Process s:
"ab##" -> ""Process t:
"c#d#" -> ""Both final strings are empty.
So the answer is:
TrueExample 3:
s = "a#c"
t = "b"Process s:
"a#c" -> "c"Process t:
"b" -> "b"The final strings differ.
So the answer is:
FalseFirst Thought: Build the Final Strings
The simplest solution is to simulate typing.
Use a list as a stack.
For each character:
- If it is a letter, push it.
- If it is
"#"and the stack is non-empty, pop one character. - If it is
"#"and the stack is empty, do nothing.
Then compare the two final stacks.
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
def build(text: str) -> str:
stack = []
for ch in text:
if ch == "#":
if stack:
stack.pop()
else:
stack.append(ch)
return "".join(stack)
return build(s) == build(t)This is correct and easy to read.
Problem With Extra Space
The stack solution stores the final characters of both strings.
That uses:
O(len(s) + len(t))extra space.
The follow-up asks whether we can solve the problem with constant extra space.
We can do that by scanning both strings from right to left.
Key Insight
Backspaces delete characters to their left.
So when scanning from right to left, we can keep a counter of how many letters should be skipped.
For one string:
| Character | Action |
|---|---|
"#" | Increase skip count |
| Letter and skip count > 0 | Skip this letter and decrease skip count |
| Letter and skip count == 0 | This letter survives |
This lets us find the next real character in the final string without building the string.
Algorithm
Use two pointers:
| Pointer | Meaning |
|---|---|
i | Current index in s |
j | Current index in t |
Both start at the end of their strings.
Repeat while either pointer is still valid:
- Move
ileft until it points to the next surviving character ins, or becomes-1. - Move
jleft until it points to the next surviving character int, or becomes-1. - If both pointers are valid, compare
s[i]andt[j]. - If only one pointer is valid, the final strings differ.
- Move both pointers one step left and continue.
If all surviving characters match, return True.
Walkthrough
Use:
s = "ab#c"
t = "ad#c"Start from the end.
For s, the last surviving character is:
"c"For t, the last surviving character is also:
"c"They match.
Move left.
In s, we see "#", so skip one letter. That deletes "b". The next surviving character is:
"a"In t, we see "#", so skip one letter. That deletes "d". The next surviving character is:
"a"They match.
Both strings are now exhausted, so return:
TrueCorrectness
The helper scan for one string returns the next character that remains after applying all backspaces to the prefix ending at the current pointer.
When it sees "#", it records one pending deletion. When it sees a normal character and there is a pending deletion, that character is deleted and the pending deletion count decreases. When it sees a normal character and there is no pending deletion, that character survives.
Therefore, each helper scan finds exactly the next surviving character from right to left.
The main loop compares the surviving characters of s and t in reverse order. If any pair differs, the final strings cannot be equal. If one string has a surviving character while the other does not, the final strings also cannot be equal.
If the loop finishes without mismatch, both final strings contain exactly the same surviving characters in the same order.
Therefore, the algorithm returns the correct result.
Complexity
Let:
n = len(s)
m = len(t)| Metric | Value | Why |
|---|---|---|
| Time | O(n + m) | Each character is processed at most once |
| Space | O(1) | Only pointers and skip counters are used |
Implementation
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
i = len(s) - 1
j = len(t) - 1
def next_valid_index(text: str, index: int) -> int:
skip = 0
while index >= 0:
if text[index] == "#":
skip += 1
index -= 1
elif skip > 0:
skip -= 1
index -= 1
else:
break
return index
while i >= 0 or j >= 0:
i = next_valid_index(s, i)
j = next_valid_index(t, j)
if i >= 0 and j >= 0:
if s[i] != t[j]:
return False
elif i >= 0 or j >= 0:
return False
i -= 1
j -= 1
return TrueCode Explanation
The two pointers start at the end:
i = len(s) - 1
j = len(t) - 1The helper function skips deleted characters:
def next_valid_index(text: str, index: int) -> int:When it sees a backspace, it increases the number of letters to skip:
if text[index] == "#":
skip += 1When it sees a letter and skip > 0, that letter is deleted:
elif skip > 0:
skip -= 1When it sees a letter and skip == 0, that letter remains in the final string, so the helper stops.
The main loop finds the next surviving character in each string:
i = next_valid_index(s, i)
j = next_valid_index(t, j)If both exist, compare them:
if s[i] != t[j]:
return FalseIf only one exists, the final strings have different lengths:
elif i >= 0 or j >= 0:
return FalseAfter comparing, move left:
i -= 1
j -= 1Testing
def run_tests():
s = Solution()
assert s.backspaceCompare("ab#c", "ad#c") is True
assert s.backspaceCompare("ab##", "c#d#") is True
assert s.backspaceCompare("a#c", "b") is False
assert s.backspaceCompare("####", "##") is True
assert s.backspaceCompare("bxj##tw", "bxo#j##tw") is True
assert s.backspaceCompare("a##c", "#a#c") is True
assert s.backspaceCompare("xywrrmp", "xywrrmu#p") is True
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
"ab#c" vs "ad#c" | Confirms normal deletion |
"ab##" vs "c#d#" | Confirms both become empty |
"a#c" vs "b" | Confirms different final strings |
| Many backspaces | Confirms empty text stays empty |
| Consecutive backspaces | Confirms multiple deletions |
| Leading backspace | Confirms backspace on empty text has no effect |
| Deletion near end | Confirms right-to-left scan handles suffixes |