title: Backspace String Compare
slug: backspace-string-compare
difficulty: easy
leetcode_id: 844
leetcode_url: https://leetcode.com/problems/backspace-string-compare/
categories:
  - strings
  - stack
  - two-pointers
patterns:
  - two-pointers

description: |
  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.

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

examples:
  - input: 's = "ab#c", t = "ad#c"'
    output: "true"
    explanation: "Both s and t become \"ac\"."
  - input: 's = "ab##", t = "c#d#"'
    output: "true"
    explanation: "Both s and t become \"\"."
  - input: 's = "a#c", t = "b"'
    output: "false"
    explanation: "s becomes \"c\" while t becomes \"b\"."

explanation:
  intuition: |
    Imagine typing on a keyboard where `#` is the backspace key. Each time you press backspace, the last character you typed gets deleted. If the text is already empty, nothing happens.

    The simplest mental model is to **simulate the typing process**: walk through each string character by character, building up the final result. When you see a letter, add it; when you see `#`, remove the last letter (if any).

    However, there's a more elegant approach that uses **O(1) space**: process both strings **from right to left**. Why right to left? Because a backspace only affects characters *before* it, not after. By starting from the end, when we encounter a `#`, we know exactly how many characters to skip. We can then compare the "valid" characters of both strings one at a time without building the full result.

    Think of it like this: instead of simulating forward typing, we're **rewinding** from the final cursor position, skipping over characters that would have been deleted.

  approach: |
    We solve this using a **Two Pointers (Reverse Traversal) Approach**:

    **Step 1: Initialise pointers and skip counters**

    - `i`: Pointer starting at the end of string `s` (index `len(s) - 1`)
    - `j`: Pointer starting at the end of string `t` (index `len(t) - 1`)
    - `skip_s`, `skip_t`: Counters tracking how many characters to skip in each string

    &nbsp;

    **Step 2: Process both strings from right to left**

    - While either pointer is valid (>= 0):
      - **Handle skips in `s`**: If current char is `#`, increment `skip_s` and move left. If `skip_s > 0` and current char isn't `#`, decrement `skip_s` and move left (skip this char)
      - **Handle skips in `t`**: Same logic with `skip_t` and pointer `j`
      - After skipping, compare the characters at positions `i` and `j`

    &nbsp;

    **Step 3: Compare characters**

    - If both pointers are valid, characters must match
    - If one pointer is valid and the other isn't, strings differ in length
    - Move both pointers left and continue

    &nbsp;

    **Step 4: Return result**

    - If we process both strings completely with all characters matching, return `true`
    - Return `false` if any mismatch occurs

    &nbsp;

    This approach works because backspaces only affect preceding characters. By going backwards and counting skips, we can identify which characters "survive" without actually building the result strings.

  common_pitfalls:
    - title: Building Full Strings (Wasteful Space)
      description: |
        A common first approach is to build the final string for both `s` and `t` using a stack or list, then compare them.

        While this works and is O(n) time, it uses **O(n) space**. The follow-up challenge asks for O(1) space, which the two-pointer approach achieves.

        For interviews, mention both approaches: stack-based for clarity, two-pointer for optimal space.
      wrong_approach: "Build both result strings, then compare"
      correct_approach: "Compare character-by-character using reverse traversal"

    - title: Processing Left to Right
      description: |
        Trying to process strings left-to-right with O(1) space is tricky because you don't know how many future backspaces will delete current characters.

        For example, in `"abc###"`, when you see `'a'`, you don't know yet that it will be deleted by a later `#`. You'd need to look ahead or store characters.

        Going **right to left** avoids this: when you see `#`, you immediately know to skip the next valid character to the left.
      wrong_approach: "Left-to-right with lookahead"
      correct_approach: "Right-to-left with skip counter"

    - title: Forgetting Edge Cases with Multiple Backspaces
      description: |
        Consider `"ab##"`: both characters get deleted. Or `"a###"`: one character deleted, two backspaces on empty text (no effect).

        Your skip counter must handle:
        - Multiple consecutive `#` characters (accumulate skips)
        - More backspaces than characters (skips that have nothing to delete)
        - Empty result strings (both pointers go past 0)

  key_takeaways:
    - "**Reverse traversal pattern**: When an operation affects preceding elements (like backspace), consider processing from the end"
    - "**Two-pointer comparison**: Instead of building and comparing, compare elements one at a time using coordinated pointers"
    - "**Space optimisation**: The stack approach is intuitive (O(n) space), but the two-pointer approach achieves O(1) space"
    - "**Skip counter technique**: Track \"pending operations\" as a counter to avoid storing intermediate results"

  time_complexity: "O(n + m). We traverse each string at most twice (once for skipping, once for comparing), where `n` and `m` are the lengths of `s` and `t`."
  space_complexity: "O(1) for two-pointer approach. We only use a constant number of variables regardless of input size. The stack approach uses O(n + m) space."

solutions:
  - approach_name: Two Pointers (Reverse Traversal)
    is_optimal: true
    code: |
      def backspace_compare(s: str, t: str) -> bool:
          # Start from the end of both strings
          i, j = len(s) - 1, len(t) - 1
          skip_s = skip_t = 0

          # Process both strings from right to left
          while i >= 0 or j >= 0:
              # Find the next valid character in s (after applying backspaces)
              while i >= 0:
                  if s[i] == '#':
                      skip_s += 1  # Count backspace
                      i -= 1
                  elif skip_s > 0:
                      skip_s -= 1  # Skip this character (it's deleted)
                      i -= 1
                  else:
                      break  # Found a valid character

              # Find the next valid character in t (after applying backspaces)
              while j >= 0:
                  if t[j] == '#':
                      skip_t += 1  # Count backspace
                      j -= 1
                  elif skip_t > 0:
                      skip_t -= 1  # Skip this character (it's deleted)
                      j -= 1
                  else:
                      break  # Found a valid character

              # Compare the valid characters
              if i >= 0 and j >= 0:
                  if s[i] != t[j]:
                      return False  # Characters don't match
              elif i >= 0 or j >= 0:
                  return False  # One string has characters left, other doesn't

              # Move to next characters
              i -= 1
              j -= 1

          return True  # All characters matched
    explanation: |
      **Time Complexity:** O(n + m) — Each character in both strings is visited at most twice.

      **Space Complexity:** O(1) — Only a constant number of variables used.

      We traverse both strings from right to left, using skip counters to track pending backspaces. When we find a `#`, we increment the skip counter. When we find a regular character with pending skips, we decrement and continue. Otherwise, we've found a "surviving" character to compare. This elegantly handles the temporal nature of backspaces without building intermediate strings.

  - approach_name: Stack Simulation
    is_optimal: false
    code: |
      def backspace_compare(s: str, t: str) -> bool:
          def build_string(string: str) -> str:
              """Simulate typing and return final result."""
              stack = []
              for char in string:
                  if char == '#':
                      if stack:  # Only pop if stack not empty
                          stack.pop()
                  else:
                      stack.append(char)
              return ''.join(stack)

          # Build both final strings and compare
          return build_string(s) == build_string(t)
    explanation: |
      **Time Complexity:** O(n + m) — Single pass through each string.

      **Space Complexity:** O(n + m) — Stack can hold up to all characters.

      This approach directly simulates the typing process using a stack. Letters get pushed, `#` causes a pop (if possible). While not space-optimal, this solution is very intuitive and easy to understand. It's a good first answer in an interview before optimising to the two-pointer approach.
