Files
codetutor/backend/data/questions/check-if-one-string-swap-can-make-strings-equal.yaml

183 lines
8.0 KiB
YAML

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.