188 lines
8.4 KiB
YAML
188 lines
8.4 KiB
YAML
title: Buddy Strings
|
|
slug: buddy-strings
|
|
difficulty: easy
|
|
leetcode_id: 859
|
|
leetcode_url: https://leetcode.com/problems/buddy-strings/
|
|
categories:
|
|
- strings
|
|
- hash-tables
|
|
patterns:
|
|
- two-pointers
|
|
|
|
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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**Step 3: Find mismatched positions**
|
|
|
|
- Iterate through both strings simultaneously
|
|
- Collect indices where `s[i] != goal[i]`
|
|
- Store these in a list called `diff`
|
|
|
|
|
|
|
|
**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.
|