Buddy Strings: One Swap to Make Strings Equal
Can you make two strings equal by swapping exactly one pair of characters in the first string? That is LeetCode 859: Buddy Strings, rated easy. The trick is that "exactly one swap" introduces a subtle special case that catches a lot of people off guard.
The problem
Given two strings s and goal, return True if you can swap two characters in s so that the result equals goal. You must swap exactly once, and you swap two positions in s (not one from each string).
Input: s = "ab", goal = "ba"
Output: True # swap s[0] and s[1]
Input: s = "ab", goal = "ab"
Output: False # strings are already equal, but no valid swap exists (all characters are unique)
Input: s = "aa", goal = "aa"
Output: True # swap s[0] and s[1], string stays the same
Input: s = "abcd", goal = "acbd"
Output: True # swap s[1] and s[2]
Breaking it down
There are two main cases to consider:
Case 1: s and goal differ at some positions. For a single swap to fix things, there must be exactly two positions where they differ. And the characters at those two positions must cross-match: s[i] == goal[j] and s[j] == goal[i].
Case 2: s and goal are already identical. You still need to perform a swap, so you need two positions with the same character. In other words, s must contain at least one duplicate character. Swapping two identical characters leaves the string unchanged, which satisfies the requirement.
If neither case applies, the answer is False.
The key insight
The core logic boils down to two checks:
- Find all indices where
s[i] != goal[i]. Call this listdiffs. - If
len(diffs) == 2: check thats[diffs[0]] == goal[diffs[1]]ands[diffs[1]] == goal[diffs[0]]. - If
len(diffs) == 0: check thatshas at least one duplicate character (use a set or frequency count). - Any other number of differences means no single swap can work.
That is the entire algorithm. No sorting, no complex data structures. Just one pass to collect differences and one check at the end.
The duplicate-character check when s == goal is the part most people forget. You can detect duplicates by comparing len(s) to len(set(s)). If the set is smaller, there is a duplicate.
Step-by-step walkthrough
Let's trace through s = "abcd" and goal = "acbd".
Step 1: Check lengths
len(s) = 4, len(goal) = 4. Lengths match, so we continue. If they differed, we would return False immediately.
Step 2: Compare character by character
Walk through each index and compare s[i] vs goal[i]. Collect indices where they differ.
Step 3: Count differences
Differences found at indices 1 and 2. s[1]="b", goal[1]="c" and s[2]="c", goal[2]="b". Exactly 2 differences is what we need.
Step 4: Verify the cross-match
Check: does s[1] == goal[2] and s[2] == goal[1]? "b" == "b" and "c" == "c". Yes! A single swap fixes it.
Step 5: Return True
Exactly 2 differences, and the values cross-match. One swap of s[1] and s[2] makes s equal to goal. Return True.
The algorithm walks through both strings once, collects the two differing positions, confirms the cross-match, and returns True.
The code
def buddyStrings(s: str, goal: str) -> bool:
if len(s) != len(goal):
return False
if s == goal:
# Need at least one duplicate character to swap
return len(set(s)) < len(s)
# Collect positions where s and goal differ
diffs = []
for i in range(len(s)):
if s[i] != goal[i]:
diffs.append(i)
# Must have exactly 2 differences that cross-match
if len(diffs) != 2:
return False
i, j = diffs
return s[i] == goal[j] and s[j] == goal[i]
Let's break down each part:
- Length check. If
sandgoalhave different lengths, no single swap (or any number of swaps) can make them equal. ReturnFalseimmediately. - Equal strings check. If the strings are already the same, you need a "free" swap. That is only possible when
shas duplicate characters.len(set(s)) < len(s)checks this in O(n) time. - Collect differences. Walk through both strings and record every index where they differ.
- Exactly 2 differences. If there are fewer than 2 or more than 2 differences, a single swap cannot fix it. With exactly 2, check that swapping those two positions in
swould producegoal.
You could also short-circuit the loop and return False as soon as you find a third difference. That is a minor optimization, but it does not change the overall complexity.
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(n) |
| Space | O(1) |
Time: One pass through both strings to collect differences. The set construction for the equal-strings case is also O(n). Total: O(n) where n is the length of the strings.
Space: The diffs list holds at most 2 entries (you can short-circuit after 3). The set holds at most 26 characters for lowercase English letters. Both are bounded by constants, so the space is O(1).
The building blocks
Pairwise comparison with difference collection
The pattern of walking two sequences in parallel and collecting positions where they differ is reusable. You see it in edit distance variations, string matching, and anywhere you need to quantify how two sequences differ.
diffs = []
for i in range(len(s)):
if s[i] != goal[i]:
diffs.append(i)
Duplicate detection with a set
Checking len(set(s)) < len(s) is a one-liner for "does this string have any repeated characters?" The set discards duplicates, so if its size is smaller than the original, at least one character appeared more than once. This same trick works for arrays, lists, or any iterable.
has_duplicate = len(set(s)) < len(s)
Edge cases
- Different lengths.
s = "ab",goal = "abc". ReturnFalseimmediately. No swap can change the length of a string. - No differences (with duplicates).
s = "aab",goal = "aab". The strings are equal andshas a duplicatea. You can swap the twoavalues and the string stays the same. ReturnTrue. - No differences (no duplicates).
s = "abc",goal = "abc". The strings are equal but every character is unique. Any swap would changes, so it could not equalgoalafter the swap. ReturnFalse. - More than 2 differences.
s = "abcd",goal = "dcba". Four positions differ. No single swap can fix all of them. ReturnFalse. - Exactly 1 difference.
s = "abc",goal = "adc". Only one position differs. A swap always changes two positions, so one difference is impossible to fix with a swap. ReturnFalse. - Cross-match fails.
s = "ab",goal = "cd". Two differences, buts[0] != goal[1]. The swap would not produce the right characters. ReturnFalse.
A common mistake is returning True whenever you see exactly 2 differences without checking the cross-match. The positions must swap to produce the correct characters, not just differ.
From understanding to recall
This problem is deceptively simple. The main logic is a single loop and a couple of checks. But the edge case where s == goal is the one that trips people up. If you remember "two cases: different strings need exactly 2 cross-matching diffs, equal strings need a duplicate," the whole solution follows naturally.
Practice by writing the solution from scratch with s = "ab", goal = "ba" and then s = "aa", goal = "aa". Make sure you handle both paths. After a few repetitions, the length check, the equal-strings branch, and the difference-collection loop will feel automatic.
Related posts
- Valid Anagram - Another string comparison problem using character frequency counting
- Isomorphic Strings - Checking structural equivalence between two strings with mapping
- Word Pattern - Pattern matching between a string and a sequence of words