859. Buddy Strings
Problem Description
You are given two strings s
and goal
. You need to determine if you can make s
equal to goal
by swapping exactly two letters in s
.
The swap operation involves selecting two different positions i
and j
in string s
(where i != j
) and exchanging the characters at those positions.
The function should return true
if exactly one swap can transform s
into goal
, and false
otherwise.
Key Points:
- You must perform exactly one swap (exchanging two characters)
- The two positions being swapped must be different (
i != j
) - If
s
is already equal togoal
, you still need to perform a swap - this is only valid if there are duplicate characters that can be swapped without changing the string
Examples:
- If
s = "ab"
andgoal = "ba"
, swapping positions 0 and 1 gives"ba"
, so returntrue
- If
s = "ab"
andgoal = "ab"
, they are already equal but there are no duplicate characters to swap, so returnfalse
- If
s = "aa"
andgoal = "aa"
, they are equal and we can swap the two 'a's without changing the string, so returntrue
- If
s = "abcd"
andgoal = "cbad"
, swapping positions 0 and 2 transforms"abcd"
to"cbad"
, so returntrue
Intuition
To solve this problem, let's think about what conditions must be met for exactly one swap to transform s
into goal
.
First, both strings must have the same length - if they don't, no single swap can make them equal.
Second, both strings must contain the same characters with the same frequencies. A swap only rearranges characters within s
, it doesn't add or remove any. So if s
has different character counts than goal
, they can never be made equal through swapping.
Now, assuming the strings have the same length and character frequencies, we need to consider two scenarios:
Scenario 1: The strings differ at exactly 2 positions
If s
and goal
differ at exactly 2 positions, say positions i
and j
, then swapping s[i]
with s[j]
should make them equal. For this to work, we need s[i] = goal[j]
and s[j] = goal[i]
. The character frequency check we already performed guarantees this condition.
Scenario 2: The strings are already identical
If s
and goal
are already the same, we still need to perform one swap. The only way this works is if there's at least one character that appears more than once in s
. We can swap two occurrences of the same character, which doesn't change the string but satisfies the requirement of performing exactly one swap.
If the strings differ at 0 positions (identical) but have no duplicate characters, or if they differ at 1, 3, or more positions, then it's impossible to make them equal with exactly one swap.
This leads us to count the number of differing positions and check for duplicate characters when needed.
Solution Approach
Let's walk through the implementation step by step:
Step 1: Check Length Equality
m, n = len(s), len(goal)
if m != n:
return False
First, we check if both strings have the same length. If they don't, it's impossible to make them equal with a single swap.
Step 2: Check Character Frequency
cnt1, cnt2 = Counter(s), Counter(goal) if cnt1 != cnt2: return False
We use Python's Counter
from the collections module to count the frequency of each character in both strings. If the character frequencies don't match, no amount of swapping within s
can make it equal to goal
. The Counter
creates a dictionary-like object where keys are characters and values are their counts.
Step 3: Count Differences
diff = sum(s[i] != goal[i] for i in range(n))
We count how many positions have different characters between s
and goal
. This uses a generator expression with sum()
to count the number of True
values (positions where characters differ).
Step 4: Determine if Valid Swap Exists
return diff == 2 or (diff == 0 and any(v > 1 for v in cnt1.values()))
The final check handles two valid cases:
diff == 2
: Exactly two positions differ. Since we've already verified the character frequencies match, swapping these two positions will make the strings equal.diff == 0 and any(v > 1 for v in cnt1.values())
: The strings are already identical, but we can still perform a valid swap if there's at least one character that appears more than once. We check this by looking at the values in our frequency counter to see if any character has a count greater than 1.
The algorithm has a time complexity of O(n)
where n
is the length of the strings, as we traverse the strings a constant number of times. The space complexity is O(k)
where k
is the number of unique characters in the strings (for the Counter objects).
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "abcd"
and goal = "cbad"
:
Step 1: Check Length Equality
len(s) = 4
,len(goal) = 4
✓- Lengths match, continue to next step
Step 2: Check Character Frequency
Counter(s) = {'a': 1, 'b': 1, 'c': 1, 'd': 1}
Counter(goal) = {'c': 1, 'b': 1, 'a': 1, 'd': 1}
- Both counters are equal (same characters with same frequencies) ✓
Step 3: Count Differences
- Compare position by position:
- Position 0:
s[0] = 'a'
,goal[0] = 'c'
→ Different - Position 1:
s[1] = 'b'
,goal[1] = 'b'
→ Same - Position 2:
s[2] = 'c'
,goal[2] = 'a'
→ Different - Position 3:
s[3] = 'd'
,goal[3] = 'd'
→ Same
- Position 0:
- Total differences:
diff = 2
Step 4: Determine if Valid Swap Exists
- Since
diff == 2
, we check if swapping positions 0 and 2 works:s[0] = 'a'
should becomegoal[0] = 'c'
s[2] = 'c'
should becomegoal[2] = 'a'
- After swapping positions 0 and 2:
"abcd"
→"cbad"
✓
- Return
true
Let's also consider the edge case where s = "aa"
and goal = "aa"
:
Steps 1-2: Length and frequency checks pass
Step 3: Count differences = 0 (strings are identical)
Step 4: Check if we can still perform a valid swap:
diff == 0
, so check if any character appears more than onceCounter(s) = {'a': 2}
, and 2 > 1 ✓- We can swap the two 'a's at positions 0 and 1, which keeps the string unchanged
- Return
true
Solution Implementation
1from collections import Counter
2from typing import List
3
4class Solution:
5 def buddyStrings(self, s: str, goal: str) -> bool:
6 """
7 Check if we can swap exactly two characters in string s to make it equal to goal.
8
9 Args:
10 s: Source string
11 goal: Target string to match after one swap
12
13 Returns:
14 True if exactly one swap can make s equal to goal, False otherwise
15 """
16 # Check if strings have the same length
17 string_length = len(s)
18 goal_length = len(goal)
19
20 if string_length != goal_length:
21 return False
22
23 # Count character frequencies in both strings
24 s_char_count = Counter(s)
25 goal_char_count = Counter(goal)
26
27 # If character frequencies don't match, strings can't be made equal
28 if s_char_count != goal_char_count:
29 return False
30
31 # Count the number of positions where characters differ
32 difference_count = sum(s[i] != goal[i] for i in range(string_length))
33
34 # Two valid cases:
35 # 1. Exactly 2 differences (swap those two positions)
36 # 2. No differences but at least one duplicate character (swap two identical chars)
37 return (difference_count == 2 or
38 (difference_count == 0 and any(count > 1 for count in s_char_count.values())))
39
1class Solution {
2 public boolean buddyStrings(String s, String goal) {
3 // Check if strings have the same length
4 int sLength = s.length();
5 int goalLength = goal.length();
6 if (sLength != goalLength) {
7 return false;
8 }
9
10 // Count differences between the two strings and character frequencies
11 int differenceCount = 0;
12 int[] sCharFrequency = new int[26]; // Frequency array for string s
13 int[] goalCharFrequency = new int[26]; // Frequency array for string goal
14
15 // Iterate through both strings simultaneously
16 for (int i = 0; i < sLength; ++i) {
17 char sChar = s.charAt(i);
18 char goalChar = goal.charAt(i);
19
20 // Update character frequencies
21 ++sCharFrequency[sChar - 'a'];
22 ++goalCharFrequency[goalChar - 'a'];
23
24 // Count positions where characters differ
25 if (sChar != goalChar) {
26 ++differenceCount;
27 }
28 }
29
30 // Check if both strings have the same character frequencies
31 // and if any character appears more than once
32 boolean hasDuplicateChar = false;
33 for (int i = 0; i < 26; ++i) {
34 // If character frequencies don't match, strings can't be made equal
35 if (sCharFrequency[i] != goalCharFrequency[i]) {
36 return false;
37 }
38 // Check if any character appears at least twice
39 if (sCharFrequency[i] > 1) {
40 hasDuplicateChar = true;
41 }
42 }
43
44 // Two valid cases for buddy strings:
45 // 1. Exactly 2 differences (can swap those two positions)
46 // 2. No differences but has duplicate characters (can swap two identical characters)
47 return differenceCount == 2 || (differenceCount == 0 && hasDuplicateChar);
48 }
49}
50
1class Solution {
2public:
3 bool buddyStrings(string s, string goal) {
4 int lengthS = s.size();
5 int lengthGoal = goal.size();
6
7 // Different lengths mean strings cannot be buddy strings
8 if (lengthS != lengthGoal) {
9 return false;
10 }
11
12 // Count number of positions where characters differ
13 int differenceCount = 0;
14
15 // Frequency arrays for characters in both strings
16 vector<int> frequencyS(26, 0);
17 vector<int> frequencyGoal(26, 0);
18
19 // Count character frequencies and differences
20 for (int i = 0; i < lengthS; ++i) {
21 ++frequencyS[s[i] - 'a'];
22 ++frequencyGoal[goal[i] - 'a'];
23
24 if (s[i] != goal[i]) {
25 ++differenceCount;
26 }
27 }
28
29 // Check if any character appears more than once (for identical strings case)
30 bool hasDuplicateChar = false;
31
32 // Verify character frequencies match and check for duplicates
33 for (int i = 0; i < 26; ++i) {
34 // If character frequencies don't match, strings can't be buddy strings
35 if (frequencyS[i] != frequencyGoal[i]) {
36 return false;
37 }
38
39 // Mark if any character appears more than once
40 if (frequencyS[i] > 1) {
41 hasDuplicateChar = true;
42 }
43 }
44
45 // Two valid cases for buddy strings:
46 // 1. Exactly 2 differences (swap those two positions)
47 // 2. No differences but has duplicate characters (swap two identical characters)
48 return differenceCount == 2 || (differenceCount == 0 && hasDuplicateChar);
49 }
50};
51
1/**
2 * Determines if two strings can become equal by swapping exactly two characters in the first string
3 * @param s - The source string to swap characters in
4 * @param goal - The target string to match after swapping
5 * @returns true if s can become goal by swapping exactly two characters, false otherwise
6 */
7function buddyStrings(s: string, goal: string): boolean {
8 const sourceLength: number = s.length;
9 const goalLength: number = goal.length;
10
11 // Strings must have the same length to be buddy strings
12 if (sourceLength !== goalLength) {
13 return false;
14 }
15
16 // Arrays to count frequency of each character (26 lowercase letters)
17 const sourceCharCount: number[] = new Array(26).fill(0);
18 const goalCharCount: number[] = new Array(26).fill(0);
19
20 // Count the number of positions where characters differ
21 let differenceCount: number = 0;
22
23 // Iterate through both strings simultaneously
24 for (let i = 0; i < goalLength; i++) {
25 // Calculate character index (0-25) and increment frequency counters
26 const sourceCharIndex: number = s.charCodeAt(i) - 'a'.charCodeAt(0);
27 const goalCharIndex: number = goal.charCodeAt(i) - 'a'.charCodeAt(0);
28
29 sourceCharCount[sourceCharIndex]++;
30 goalCharCount[goalCharIndex]++;
31
32 // Track positions where characters don't match
33 if (s[i] !== goal[i]) {
34 differenceCount++;
35 }
36 }
37
38 // Verify both strings have the same character frequencies
39 for (let i = 0; i < 26; i++) {
40 if (sourceCharCount[i] !== goalCharCount[i]) {
41 return false;
42 }
43 }
44
45 // Valid buddy strings if:
46 // 1. Exactly 2 positions differ (can swap these two)
47 // 2. Strings are identical AND at least one character appears more than once (can swap duplicates)
48 return differenceCount === 2 || (differenceCount === 0 && sourceCharCount.some((count: number) => count > 1));
49}
50
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs the following operations:
- Creating
Counter(s)
:O(n)
where n is the length of string s - Creating
Counter(goal)
:O(n)
where n is the length of string goal - Comparing two Counter objects
cnt1 != cnt2
:O(n)
in worst case (needs to check all unique characters) - Computing differences using list comprehension:
O(n)
to iterate through all indices - Checking if any value > 1 in
cnt1.values()
:O(k)
where k is the number of unique characters, and k ≤ n
Since all operations are linear and performed sequentially, the overall time complexity is O(n)
.
Space Complexity: O(k)
The algorithm uses additional space for:
Counter(s)
:O(k₁)
where k₁ is the number of unique characters in sCounter(goal)
:O(k₂)
where k₂ is the number of unique characters in goal- The generator expression for computing diff doesn't create additional space beyond
O(1)
In the worst case where all characters are unique, k can be at most min(n, 26) for lowercase English letters. Since the alphabet size is constant (26 for lowercase letters), the space complexity can also be considered O(1)
for practical purposes. However, if we consider the general case without alphabet constraints, the space complexity is O(k)
where k ≤ n.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting the Case Where Strings Are Already Equal
The Problem:
Many developers initially write code that only checks if there are exactly 2 differences between the strings, forgetting that when s
and goal
are already identical, we still need to perform exactly one swap. This leads to incorrect handling of cases like:
s = "ab"
,goal = "ab"
→ Should returnFalse
(no duplicates to swap)s = "aa"
,goal = "aa"
→ Should returnTrue
(can swap the two 'a's)
Incorrect Implementation:
def buddyStrings(self, s: str, goal: str) -> bool:
if len(s) != len(goal):
return False
differences = []
for i in range(len(s)):
if s[i] != goal[i]:
differences.append(i)
# WRONG: Only checking for 2 differences
return len(differences) == 2
The Solution: Always handle both cases explicitly:
- When there are exactly 2 differences
- When there are 0 differences AND at least one duplicate character exists
# Correct approach
difference_count = sum(s[i] != goal[i] for i in range(len(s)))
return (difference_count == 2 or
(difference_count == 0 and any(count > 1 for count in Counter(s).values())))
Pitfall 2: Not Validating That the Two Differences Are Actually Swappable
The Problem: When finding exactly 2 differences, some implementations forget to verify that swapping those two positions would actually make the strings equal. Just having 2 differences doesn't guarantee a valid swap.
Incorrect Implementation:
def buddyStrings(self, s: str, goal: str) -> bool:
if len(s) != len(goal):
return False
diff_positions = []
for i in range(len(s)):
if s[i] != goal[i]:
diff_positions.append(i)
# WRONG: Not checking if the swap actually works
if len(diff_positions) == 2:
return True # This assumes any 2 differences can be swapped
return False
The Solution: Either explicitly check that swapping the two different positions produces the correct result, OR use character frequency counting to ensure the strings contain the same characters:
# Solution 1: Explicit swap verification
if len(diff_positions) == 2:
i, j = diff_positions[0], diff_positions[1]
return s[i] == goal[j] and s[j] == goal[i]
# Solution 2: Character frequency validation (as in our main solution)
s_char_count = Counter(s)
goal_char_count = Counter(goal)
if s_char_count != goal_char_count:
return False # Different character sets means no valid swap exists
Pitfall 3: Using Set to Check for Duplicates When Strings Are Equal
The Problem:
When checking if identical strings have duplicate characters (to allow a valid swap), using len(set(s)) < len(s)
seems elegant but can be inefficient for very long strings with many unique characters.
Less Efficient Implementation:
if difference_count == 0:
# Creates a full set of all characters
return len(set(s)) < len(s)
The Solution:
Use early termination with any()
to stop as soon as a duplicate is found:
# More efficient - stops as soon as a duplicate is found
if difference_count == 0:
return any(count > 1 for count in Counter(s).values())
This approach is particularly beneficial when dealing with strings that have duplicates early in the character frequency distribution, as it doesn't need to process all unique characters.
Which data structure is used to implement recursion?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!