Skip to content

Commit bf48799

Browse files
authored
Merge pull request #201 from BrianLusina/feat/backspace-string-compare
feat(algorithms, two-pointers): backspace string compare
2 parents 70bb453 + 10567e6 commit bf48799

8 files changed

Lines changed: 233 additions & 0 deletions

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@
466466
* [Test Append Chars To Make Subsequence](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/append_chars_to_make_subsequence/test_append_chars_to_make_subsequence.py)
467467
* Array 3 Pointers
468468
* [Test Array 3 Pointers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/array_3_pointers/test_array_3_pointers.py)
469+
* Backspace String Compare
470+
* [Test Backspace String Compare](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/backspace_string_compare/test_backspace_string_compare.py)
469471
* Container With Most Water
470472
* [Test Container With Most Water](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/container_with_most_water/test_container_with_most_water.py)
471473
* Count Pairs
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Backspace String Compare
2+
3+
Given two strings s and t, return true if they are equal when both are typed into empty text editors. '#' means a
4+
backspace character.
5+
6+
Note that after backspacing an empty text, the text will continue empty.
7+
8+
## Examples
9+
10+
![Example 1](./images/examples/backspace_string_compare_example_1.png)
11+
![Example 2](./images/examples/backspace_string_compare_example_2.png)
12+
![Example 3](./images/examples/backspace_string_compare_example_3.png)
13+
![Example 4](./images/examples/backspace_string_compare_example_4.png)
14+
15+
Example 5:
16+
17+
```text
18+
Input: s = "ab#c", t = "ad#c"
19+
Output: true
20+
Explanation: Both s and t become "ac".
21+
```
22+
23+
Example 6:
24+
25+
```text
26+
Input: s = "ab##", t = "c#d#"
27+
Output: true
28+
Explanation: Both s and t become "".
29+
```
30+
31+
Example 7:
32+
```text
33+
Input: s = "a#c", t = "b"
34+
Output: false
35+
Explanation: s becomes "c" while t becomes "b".
36+
```
37+
38+
## Constraints
39+
40+
- 1 <= s.length, t.length <= 200
41+
- s and t only contain lowercase letters and '#' characters.
42+
43+
> Follow up: Can you solve it in O(n) time and O(1) space?
44+
45+
## Topics
46+
47+
- Two Pointers
48+
- String
49+
- Stack
50+
- Simulation
51+
52+
## Solution(s)
53+
54+
- [Two Pointers](#two-pointers)
55+
- [Build String](#build-string)
56+
57+
### Two Pointers
58+
59+
When writing a character, it may or may not be part of the final string depending on how many backspace keystrokes occur
60+
in the future.
61+
62+
If instead we iterate through the string in reverse, then we will know how many backspace characters we have seen, and
63+
therefore whether the result includes our character.
64+
65+
The key insight is that backspace characters affect only the characters to their left, which means if we traverse both
66+
strings from right to left, we can determine which characters are truly “visible” (i.e., not cancelled by a '#') and
67+
compare them on the fly without ever building the final strings. We maintain two pointers, one for each string, and a
68+
skip counter for each that tracks how many upcoming characters should be skipped due to pending backspaces. Whenever both
69+
pointers land on a valid character simultaneously, we compare them directly and move on.
70+
71+
#### Algorithm
72+
73+
Iterate through the string in reverse. If we see a backspace character, the next non-backspace character is skipped. If
74+
a character isn't skipped, it is part of the final answer.
75+
76+
#### Complexity Analysis
77+
78+
- **Time Complexity**: O(m + n), where m, n are the lengths of `s` and `t` respectively. Because each character in `s`
79+
(of length `m`) and each character in `t` of length `n` is visited at most once by its respective pointer, making the
80+
total work proportional to the combined length of both strings
81+
- **Space Complexity**: O(1). This is because only a fixed number of variables(the pointers) are used regardless of the
82+
input sizes, with no auxiliary data structures or string reconstruction required.
83+
84+
### Build String
85+
86+
Let's individually build the result of each string (build(S) and build(T)), then compare if they are equal.
87+
88+
Algorithm
89+
90+
To build the result of a string build(S), we'll use a stack based approach, simulating the result of each keystroke.
91+
92+
#### Complexity Analysis
93+
94+
- Time Complexity: O(M+N), where M,N are the lengths of S and T respectively.
95+
- Space Complexity: O(M+N).
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from typing import Generator, Any
2+
from itertools import zip_longest
3+
4+
5+
def backspace_compare_two_pointers(s: str, t: str) -> bool:
6+
def f(word: str) -> Generator[str, Any, None]:
7+
skip = 0
8+
for x in reversed(word):
9+
if x == "#":
10+
skip += 1
11+
elif skip:
12+
skip -= 1
13+
else:
14+
yield x
15+
16+
return all(x == y for x, y in zip_longest(f(s), f(t)))
17+
18+
19+
def backspace_compare_two_pointers_2(s: str, t: str) -> bool:
20+
# Initialize pointers at the end of each string
21+
pointer_s, pointer_t = len(s) - 1, len(t) - 1
22+
# Track pending backspaces for each string
23+
skip_s, skip_t = 0, 0
24+
25+
# Continue while there are characters left to process in either string
26+
while pointer_s >= 0 or pointer_t >= 0:
27+
# Advance pointer in s to find the next valid character
28+
while pointer_s >= 0:
29+
if s[pointer_s] == "#":
30+
# Increment skip count and move pointer left
31+
skip_s += 1
32+
pointer_s -= 1
33+
elif skip_s > 0:
34+
# This character is cancelled by a backspace; consume skip and move left
35+
skip_s -= 1
36+
pointer_s -= 1
37+
else:
38+
# Found a valid character in s
39+
break
40+
41+
# Advance pointer in t to find the next valid character
42+
while pointer_t >= 0:
43+
if t[pointer_t] == "#":
44+
# Increment skip count and move pointer left
45+
skip_t += 1
46+
pointer_t -= 1
47+
elif skip_t > 0:
48+
# This character is cancelled by a backspace; consume skip and move left
49+
skip_t -= 1
50+
pointer_t -= 1
51+
else:
52+
# Found a valid character in t
53+
break
54+
55+
# Compare the current valid characters from both strings
56+
if pointer_s >= 0 and pointer_t >= 0:
57+
# If characters differ, strings are not equal
58+
if s[pointer_s] != t[pointer_t]:
59+
return False
60+
elif pointer_s >= 0 or pointer_t >= 0:
61+
# One string has characters remaining while the other is exhausted
62+
return False
63+
64+
# Move both pointers left to continue comparison
65+
pointer_s -= 1
66+
pointer_t -= 1
67+
68+
# All characters matched
69+
return True
70+
71+
72+
def backspace_compare_build_string(s: str, t: str) -> bool:
73+
def build(word: str) -> str:
74+
ans = []
75+
for c in word:
76+
if c != "#":
77+
ans.append(c)
78+
elif ans:
79+
ans.pop()
80+
return "".join(ans)
81+
82+
return build(s) == build(t)
25.7 KB
Loading
27 KB
Loading
28.8 KB
Loading
29.8 KB
Loading
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import unittest
2+
from parameterized import parameterized
3+
from utils.test_utils import custom_test_name_func
4+
from algorithms.two_pointers.backspace_string_compare import (
5+
backspace_compare_two_pointers,
6+
backspace_compare_two_pointers_2,
7+
backspace_compare_build_string,
8+
)
9+
10+
BACKSPACE_STRING_COMPARE_TEST_CASES = [
11+
("x##y", "y", True),
12+
("abc###", "def###", True),
13+
("hello#world", "hellworld", True),
14+
("a##b##c", "c", True),
15+
("coding", "coding", True),
16+
("ab#c", "ac", True),
17+
("ab#c", "bc", False),
18+
("abc###", "xyz###", True),
19+
("#", "#", True),
20+
("ab#c", "ad#c", True),
21+
("ab##", "c#d#", True),
22+
("a#c", "b", False),
23+
]
24+
25+
26+
class BackspaceStringCompareTestCase(unittest.TestCase):
27+
@parameterized.expand(
28+
BACKSPACE_STRING_COMPARE_TEST_CASES, name_func=custom_test_name_func
29+
)
30+
def test_backspace_string_compare_two_pointers(
31+
self, s: str, t: str, expected: bool
32+
):
33+
actual = backspace_compare_two_pointers(s, t)
34+
self.assertEqual(expected, actual)
35+
36+
@parameterized.expand(
37+
BACKSPACE_STRING_COMPARE_TEST_CASES, name_func=custom_test_name_func
38+
)
39+
def test_backspace_string_compare_two_pointers_2(
40+
self, s: str, t: str, expected: bool
41+
):
42+
actual = backspace_compare_two_pointers_2(s, t)
43+
self.assertEqual(expected, actual)
44+
45+
@parameterized.expand(
46+
BACKSPACE_STRING_COMPARE_TEST_CASES, name_func=custom_test_name_func
47+
)
48+
def test_backspace_compare_build_string(self, s: str, t: str, expected: bool):
49+
actual = backspace_compare_build_string(s, t)
50+
self.assertEqual(expected, actual)
51+
52+
53+
if __name__ == "__main__":
54+
unittest.main()

0 commit comments

Comments
 (0)