Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,8 @@
* [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)
* Array 3 Pointers
* [Test Array 3 Pointers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/array_3_pointers/test_array_3_pointers.py)
* Backspace String Compare
* [Test Backspace String Compare](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/backspace_string_compare/test_backspace_string_compare.py)
* Container With Most Water
* [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)
* Count Pairs
Expand Down
95 changes: 95 additions & 0 deletions algorithms/two_pointers/backspace_string_compare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Backspace String Compare

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](./images/examples/backspace_string_compare_example_1.png)
![Example 2](./images/examples/backspace_string_compare_example_2.png)
![Example 3](./images/examples/backspace_string_compare_example_3.png)
![Example 4](./images/examples/backspace_string_compare_example_4.png)

Example 5:

```text
Input: s = "ab#c", t = "ad#c"
Output: true
Explanation: Both s and t become "ac".
```

Example 6:

```text
Input: s = "ab##", t = "c#d#"
Output: true
Explanation: Both s and t become "".
```

Example 7:
```text
Input: s = "a#c", t = "b"
Output: false
Explanation: s becomes "c" while t becomes "b".
```

## Constraints

- 1 <= s.length, t.length <= 200
- s and t only contain lowercase letters and '#' characters.

> Follow up: Can you solve it in O(n) time and O(1) space?

## Topics

- Two Pointers
- String
- Stack
- Simulation

## Solution(s)

- [Two Pointers](#two-pointers)
- [Build String](#build-string)

### Two Pointers

When writing a character, it may or may not be part of the final string depending on how many backspace keystrokes occur
in the future.

If instead we iterate through the string in reverse, then we will know how many backspace characters we have seen, and
therefore whether the result includes our character.

The key insight is that backspace characters affect only the characters to their left, which means if we traverse both
strings from right to left, we can determine which characters are truly “visible” (i.e., not cancelled by a '#') and
compare them on the fly without ever building the final strings. We maintain two pointers, one for each string, and a
skip counter for each that tracks how many upcoming characters should be skipped due to pending backspaces. Whenever both
pointers land on a valid character simultaneously, we compare them directly and move on.

#### Algorithm

Iterate through the string in reverse. If we see a backspace character, the next non-backspace character is skipped. If
a character isn't skipped, it is part of the final answer.

#### Complexity Analysis

- **Time Complexity**: O(m + n), where m, n are the lengths of `s` and `t` respectively. Because each character in `s`
(of length `m`) and each character in `t` of length `n` is visited at most once by its respective pointer, making the
total work proportional to the combined length of both strings
- **Space Complexity**: O(1). This is because only a fixed number of variables(the pointers) are used regardless of the
input sizes, with no auxiliary data structures or string reconstruction required.

### Build String

Let's individually build the result of each string (build(S) and build(T)), then compare if they are equal.

Algorithm

To build the result of a string build(S), we'll use a stack based approach, simulating the result of each keystroke.

#### Complexity Analysis

- Time Complexity: O(M+N), where M,N are the lengths of S and T respectively.
- Space Complexity: O(M+N).
82 changes: 82 additions & 0 deletions algorithms/two_pointers/backspace_string_compare/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from typing import Generator, Any
from itertools import zip_longest


def backspace_compare_two_pointers(s: str, t: str) -> bool:
def f(word: str) -> Generator[str, Any, None]:
skip = 0
for x in reversed(word):
if x == "#":
skip += 1
elif skip:
skip -= 1
else:
yield x

return all(x == y for x, y in zip_longest(f(s), f(t)))


def backspace_compare_two_pointers_2(s: str, t: str) -> bool:
# Initialize pointers at the end of each string
pointer_s, pointer_t = len(s) - 1, len(t) - 1
# Track pending backspaces for each string
skip_s, skip_t = 0, 0

# Continue while there are characters left to process in either string
while pointer_s >= 0 or pointer_t >= 0:
# Advance pointer in s to find the next valid character
while pointer_s >= 0:
if s[pointer_s] == "#":
# Increment skip count and move pointer left
skip_s += 1
pointer_s -= 1
elif skip_s > 0:
# This character is cancelled by a backspace; consume skip and move left
skip_s -= 1
pointer_s -= 1
else:
# Found a valid character in s
break

# Advance pointer in t to find the next valid character
while pointer_t >= 0:
if t[pointer_t] == "#":
# Increment skip count and move pointer left
skip_t += 1
pointer_t -= 1
elif skip_t > 0:
# This character is cancelled by a backspace; consume skip and move left
skip_t -= 1
pointer_t -= 1
else:
# Found a valid character in t
break

# Compare the current valid characters from both strings
if pointer_s >= 0 and pointer_t >= 0:
# If characters differ, strings are not equal
if s[pointer_s] != t[pointer_t]:
return False
elif pointer_s >= 0 or pointer_t >= 0:
# One string has characters remaining while the other is exhausted
return False

# Move both pointers left to continue comparison
pointer_s -= 1
pointer_t -= 1

# All characters matched
return True


def backspace_compare_build_string(s: str, t: str) -> bool:
def build(word: str) -> str:
ans = []
for c in word:
if c != "#":
ans.append(c)
elif ans:
ans.pop()
return "".join(ans)

return build(s) == build(t)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import unittest
from parameterized import parameterized
from utils.test_utils import custom_test_name_func
from algorithms.two_pointers.backspace_string_compare import (
backspace_compare_two_pointers,
backspace_compare_two_pointers_2,
backspace_compare_build_string,
)

BACKSPACE_STRING_COMPARE_TEST_CASES = [
("x##y", "y", True),
("abc###", "def###", True),
("hello#world", "hellworld", True),
("a##b##c", "c", True),
("coding", "coding", True),
("ab#c", "ac", True),
("ab#c", "bc", False),
("abc###", "xyz###", True),
("#", "#", True),
("ab#c", "ad#c", True),
("ab##", "c#d#", True),
("a#c", "b", False),
]


class BackspaceStringCompareTestCase(unittest.TestCase):
@parameterized.expand(
BACKSPACE_STRING_COMPARE_TEST_CASES, name_func=custom_test_name_func
)
def test_backspace_string_compare_two_pointers(
self, s: str, t: str, expected: bool
):
actual = backspace_compare_two_pointers(s, t)
self.assertEqual(expected, actual)

@parameterized.expand(
BACKSPACE_STRING_COMPARE_TEST_CASES, name_func=custom_test_name_func
)
def test_backspace_string_compare_two_pointers_2(
self, s: str, t: str, expected: bool
):
actual = backspace_compare_two_pointers_2(s, t)
self.assertEqual(expected, actual)

@parameterized.expand(
BACKSPACE_STRING_COMPARE_TEST_CASES, name_func=custom_test_name_func
)
def test_backspace_compare_build_string(self, s: str, t: str, expected: bool):
actual = backspace_compare_build_string(s, t)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
Loading