title: Check if One String Swap Can Make Strings Equal
slug: check-if-one-string-swap-can-make-strings-equal
difficulty: easy
leetcode_id: 1790
leetcode_url: https://leetcode.com/problems/check-if-one-string-swap-can-make-strings-equal/
categories:
  - strings
  - hash-tables
patterns:
  - slug: two-pointers
    is_optimal: true

function_signature: "def are_almost_equal(s1: str, s2: str) -> bool:"

test_cases:
  visible:
    - input: { s1: "bank", s2: "kanb" }
      expected: true
    - input: { s1: "attack", s2: "defend" }
      expected: false
    - input: { s1: "kelb", s2: "kelb" }
      expected: true
  hidden:
    - input: { s1: "a", s2: "a" }
      expected: true
    - input: { s1: "ab", s2: "ba" }
      expected: true
    - input: { s1: "ab", s2: "cd" }
      expected: false
    - input: { s1: "abc", s2: "adc" }
      expected: false
    - input: { s1: "abcd", s2: "dcba" }
      expected: false

description: |
  You are given two strings `s1` and `s2` of equal length. A **string swap** is an operation where you choose two indices in a string (not necessarily different) and swap the characters at these indices.

  Return `true` *if it is possible to make both strings equal by performing **at most one string swap** on **exactly one** of the strings.* Otherwise, return `false`.

constraints: |
  - `1 <= s1.length, s2.length <= 100`
  - `s1.length == s2.length`
  - `s1` and `s2` consist of only lowercase English letters.

examples:
  - input: 's1 = "bank", s2 = "kanb"'
    output: "true"
    explanation: "Swap the first character with the last character of s2 to make \"bank\"."
  - input: 's1 = "attack", s2 = "defend"'
    output: "false"
    explanation: "It is impossible to make them equal with one string swap."
  - input: 's1 = "kelb", s2 = "kelb"'
    output: "true"
    explanation: "The two strings are already equal, so no string swap operation is required."

explanation:
  intuition: |
    Think of this problem as finding **mismatches** between the two strings. Since we can only perform at most one swap, there's a strict limit on how different the strings can be.

    Imagine laying both strings side by side and comparing character by character. If they're already identical, we're done — no swap needed. If they differ at exactly two positions, we might be able to fix it with a single swap. But if they differ at more than two positions, no single swap can help.

    The key insight is this: for a single swap to work, the characters at the two mismatched positions must be **cross-matched**. If `s1[i] != s2[i]` and `s1[j] != s2[j]`, then swapping works only if `s1[i] == s2[j]` and `s1[j] == s2[i]`.

    Think of it like having two pairs of socks that got mixed up — you can only fix it if each sock from the first pair matches its counterpart in the second pair.

  approach: |
    We solve this by finding all positions where the strings differ and checking if a swap can fix them.

    **Step 1: Find all differing positions**

    - Iterate through both strings simultaneously
    - Track indices where `s1[i] != s2[i]`
    - Store these indices in a list called `diffs`

    &nbsp;

    **Step 2: Analyse the number of differences**

    - If `len(diffs) == 0`: Strings are already equal, return `true`
    - If `len(diffs) != 2`: Cannot fix with exactly one swap, return `false`
    - A single swap affects exactly two positions, so we need exactly two differences

    &nbsp;

    **Step 3: Verify the cross-match condition**

    - Let `i` and `j` be the two differing indices
    - Check if `s1[i] == s2[j]` and `s1[j] == s2[i]`
    - If both conditions hold, swapping positions `i` and `j` in either string makes them equal
    - Return the result of this check

  common_pitfalls:
    - title: Forgetting the Zero-Difference Case
      description: |
        When both strings are already equal, the answer is `true` — no swap is required.

        Some solutions incorrectly require exactly two differences, but the problem says "at most one" swap. Zero swaps is a valid solution when strings match.
      wrong_approach: "Requiring exactly 2 differences"
      correct_approach: "Accept 0 or 2 differences as valid"

    - title: Only Counting Differences Without Verifying Characters
      description: |
        Finding exactly two mismatched positions is necessary but not sufficient.

        For example, `s1 = "ab"` and `s2 = "cd"` have two differences, but swapping won't help because the characters don't match across positions. You must verify `s1[i] == s2[j]` and `s1[j] == s2[i]`.
      wrong_approach: "Only checking if there are exactly 2 differences"
      correct_approach: "Also verify the cross-match condition"

    - title: Checking for One Difference
      description: |
        If there's exactly one position where the strings differ, no single swap can fix it.

        A swap always affects two positions. Swapping two identical characters at the same position is allowed but doesn't help — you'd still have that one mismatch.
      wrong_approach: "Thinking one difference can be fixed by swapping"
      correct_approach: "Return false for exactly 1 difference"

  key_takeaways:
    - "**Count and verify**: When a problem asks about limited operations, count what needs to change and verify the operation can achieve it"
    - "**Cross-match pattern**: For swap-based problems, the key insight is often that elements must match in a crossed pattern"
    - "**Edge cases matter**: The zero-difference case (already equal) is easy to overlook but critical"
    - "**Simple iteration works**: Don't overcomplicate with hash maps when a single pass collecting indices suffices"

  time_complexity: "O(n). We traverse both strings once to find differing positions."
  space_complexity: "O(1). We store at most 2 indices in the differences list (we can early-exit if more)."

solutions:
  - approach_name: Single Pass with Difference Tracking
    is_optimal: true
    code: |
      def are_almost_equal(s1: str, s2: str) -> bool:
          # Collect indices where characters differ
          diffs = []

          for i in range(len(s1)):
              if s1[i] != s2[i]:
                  diffs.append(i)
                  # Early exit: more than 2 differences means impossible
                  if len(diffs) > 2:
                      return False

          # Case 1: Already equal (0 differences)
          if len(diffs) == 0:
              return True

          # Case 2: Exactly 1 difference — can't fix with a swap
          if len(diffs) == 1:
              return False

          # Case 3: Exactly 2 differences — check cross-match
          i, j = diffs[0], diffs[1]
          return s1[i] == s2[j] and s1[j] == s2[i]
    explanation: |
      **Time Complexity:** O(n) — Single pass through both strings.

      **Space Complexity:** O(1) — We store at most 2 indices.

      We iterate once, collecting positions where characters differ. With early exit when we find more than 2 differences, we ensure optimal performance. The final check verifies that swapping would actually make the strings equal.

  - approach_name: Direct Character Comparison
    is_optimal: true
    code: |
      def are_almost_equal(s1: str, s2: str) -> bool:
          # Find differing positions
          diffs = [i for i in range(len(s1)) if s1[i] != s2[i]]

          # Already equal
          if not diffs:
              return True

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

          # Verify cross-match: swapping these positions would work
          i, j = diffs
          return s1[i] == s2[j] and s1[j] == s2[i]
    explanation: |
      **Time Complexity:** O(n) — List comprehension iterates through all characters.

      **Space Complexity:** O(n) in worst case — The list stores all differing indices.

      This is a more Pythonic version using list comprehension. While slightly less efficient (no early exit, stores all differences), it's cleaner for small inputs. The logic remains the same: find differences, check count, verify cross-match.
