title: Buddy Strings
slug: buddy-strings
difficulty: easy
leetcode_id: 859
leetcode_url: https://leetcode.com/problems/buddy-strings/
categories:
  - strings
  - hash-tables
patterns:
  - slug: two-pointers
    is_optimal: true

function_signature: "def buddy_strings(s: str, goal: str) -> bool:"

test_cases:
  visible:
    - input: { s: "ab", goal: "ba" }
      expected: true
    - input: { s: "ab", goal: "ab" }
      expected: false
    - input: { s: "aa", goal: "aa" }
      expected: true
  hidden:
    - input: { s: "aaaaaaabc", goal: "aaaaaaacb" }
      expected: true
    - input: { s: "abcd", goal: "badc" }
      expected: false
    - input: { s: "a", goal: "a" }
      expected: false
    - input: { s: "ab", goal: "ca" }
      expected: false
    - input: { s: "abab", goal: "abab" }
      expected: true
    - input: { s: "abcdef", goal: "abcfed" }
      expected: false

description: |
  Given two strings `s` and `goal`, return `true` *if you can swap two letters in* `s` *so the result is equal to* `goal`, *otherwise, return* `false`.

  Swapping letters is defined as taking two indices `i` and `j` (0-indexed) such that `i != j` and swapping the characters at `s[i]` and `s[j]`.

  For example, swapping at indices `0` and `2` in `"abcd"` results in `"cbad"`.

constraints: |
  - `1 <= s.length, goal.length <= 2 * 10^4`
  - `s` and `goal` consist of lowercase letters

examples:
  - input: 's = "ab", goal = "ba"'
    output: "true"
    explanation: "You can swap s[0] = 'a' and s[1] = 'b' to get \"ba\", which is equal to goal."
  - input: 's = "ab", goal = "ab"'
    output: "false"
    explanation: "The only letters you can swap are s[0] = 'a' and s[1] = 'b', which results in \"ba\" != goal."
  - input: 's = "aa", goal = "aa"'
    output: "true"
    explanation: "You can swap s[0] = 'a' and s[1] = 'a' to get \"aa\", which is equal to goal."

explanation:
  intuition: |
    Think of this problem as a **character mismatch puzzle**. You have two strings and exactly one swap to make them equal.

    The key insight is that a valid swap creates a very specific pattern: if two strings differ, they must differ at **exactly two positions**, and the characters at those positions must be **swapped mirrors** of each other. For example, if `s = "ab"` and `goal = "ba"`, position 0 has `'a'` vs `'b'` and position 1 has `'b'` vs `'a'` — a perfect cross-swap.

    But there's a subtle edge case: what if the strings are **already identical**? You still must perform exactly one swap. The only way to swap two characters and end up with the same string is if you swap **two identical characters**. This means the string must have at least one repeated character (like `"aa"` or `"aab"`).

    So the problem reduces to two distinct cases:
    1. **Strings differ**: Find exactly 2 mismatched positions where swapping fixes both
    2. **Strings are identical**: Check if any character appears more than once

  approach: |
    We solve this by handling the two cases separately:

    **Step 1: Check length equality**

    - If `len(s) != len(goal)`, return `False` immediately
    - Swapping characters can never change string length

    &nbsp;

    **Step 2: Handle identical strings**

    - If `s == goal`, we need to check if a "self-swap" is possible
    - This requires at least one duplicate character in `s`
    - Use a set to detect duplicates: if `len(set(s)) < len(s)`, duplicates exist
    - Return `True` if duplicates exist, `False` otherwise

    &nbsp;

    **Step 3: Find mismatched positions**

    - Iterate through both strings simultaneously
    - Collect indices where `s[i] != goal[i]`
    - Store these in a list called `diff`

    &nbsp;

    **Step 4: Validate the swap**

    - If `len(diff) != 2`, return `False` (must have exactly 2 mismatches)
    - Let the two mismatch indices be `i` and `j`
    - Check if swapping fixes both: `s[i] == goal[j]` and `s[j] == goal[i]`
    - Return `True` if both conditions hold, `False` otherwise

  common_pitfalls:
    - title: Forgetting the Identical Strings Case
      description: |
        When `s == goal`, many solutions incorrectly return `False` because "no swap is needed."

        But the problem requires you to **perform exactly one swap**. If `s = "ab"` and `goal = "ab"`, swapping any two distinct characters gives `"ba" != "ab"`, so the answer is `False`.

        However, if `s = "aa"` and `goal = "aa"`, swapping the two `'a'` characters still gives `"aa" == "aa"`, so the answer is `True`.

        Always check for duplicate characters when strings are identical.
      wrong_approach: "Return False when s == goal"
      correct_approach: "Check for duplicate characters when s == goal"

    - title: Not Checking for Exactly Two Differences
      description: |
        Some solutions only verify that swapping works at the first two mismatched positions without confirming there are exactly two mismatches.

        For example, with `s = "abcd"` and `goal = "badc"`, positions 0-1 form a valid swap pair (`'a'`/`'b'`), but positions 2-3 also differ (`'c'`/`'d'`). One swap cannot fix four differences!

        Always count the total number of mismatched positions and verify it equals exactly 2.
      wrong_approach: "Only check if first two mismatches can be swapped"
      correct_approach: "Verify exactly 2 mismatches exist before checking swap validity"

    - title: Ignoring Length Mismatch
      description: |
        If `s` and `goal` have different lengths, no amount of swapping within `s` can make them equal.

        This should be the first check to avoid index-out-of-bounds errors when comparing characters.
      wrong_approach: "Start comparing characters without length check"
      correct_approach: "Return False immediately if lengths differ"

  key_takeaways:
    - "**Case analysis**: Breaking problems into distinct cases (identical vs different strings) simplifies logic"
    - "**Duplicate detection**: Using `len(set(s)) < len(s)` is a clean O(n) way to check for duplicates"
    - "**Exactly-k pattern**: When a problem says 'exactly k operations', verify both minimum and maximum constraints"
    - "**Edge case awareness**: The identical strings case is a classic interview trap — always consider what happens when inputs are equal"

  time_complexity: "O(n). We traverse both strings once to find mismatches, and potentially build a set of characters."
  space_complexity: "O(n). In the worst case, we store all characters in a set when checking for duplicates. The diff list uses O(1) space since it contains at most 2 elements."

solutions:
  - approach_name: Single Pass with Case Analysis
    is_optimal: true
    code: |
      def buddy_strings(s: str, goal: str) -> bool:
          # Different lengths? Impossible to match with a swap
          if len(s) != len(goal):
              return False

          # Identical strings: need duplicate chars for valid self-swap
          if s == goal:
              # If any char appears twice, we can swap them
              return len(set(s)) < len(s)

          # Find all positions where characters differ
          diff = []
          for i in range(len(s)):
              if s[i] != goal[i]:
                  diff.append(i)

          # Must have exactly 2 differences for one swap to work
          if len(diff) != 2:
              return False

          # Check if swapping these positions makes strings equal
          i, j = diff
          return s[i] == goal[j] and s[j] == goal[i]
    explanation: |
      **Time Complexity:** O(n) — Single pass through both strings.

      **Space Complexity:** O(n) — Set storage for duplicate detection in the identical strings case.

      We handle two cases: (1) identical strings require a duplicate character for a valid self-swap, and (2) different strings need exactly two mismatched positions that form a valid swap pair.

  - approach_name: Counter-Based Approach
    is_optimal: false
    code: |
      from collections import Counter

      def buddy_strings(s: str, goal: str) -> bool:
          # Different lengths can never match
          if len(s) != len(goal):
              return False

          # Character frequencies must match for any swap to work
          if Counter(s) != Counter(goal):
              return False

          # Find positions where strings differ
          diff = [i for i in range(len(s)) if s[i] != goal[i]]

          # Case 1: Identical strings - need a duplicate for self-swap
          if len(diff) == 0:
              return len(s) != len(set(s))

          # Case 2: Exactly 2 differences - verify swap works
          if len(diff) == 2:
              i, j = diff
              return s[i] == goal[j] and s[j] == goal[i]

          # Any other number of differences: impossible
          return False
    explanation: |
      **Time Complexity:** O(n) — Building Counter objects and finding differences.

      **Space Complexity:** O(k) — Where k is the alphabet size (26 for lowercase letters).

      This approach first verifies character frequencies match (a necessary condition), then handles the same cases as the optimal solution. The Counter check is redundant when we verify the swap, but provides an early exit for obviously invalid cases.
