182 lines
7.9 KiB
YAML
182 lines
7.9 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:
|
|
- two-pointers
|
|
|
|
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`
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**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.
|