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   **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`   **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   **Step 4: Return result** - If we process both strings completely with all characters matching, return `true` - Return `false` if any mismatch occurs   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.