1790. Check if One String Swap Can Make Strings Equal
Problem Description
You are given two strings s1
and s2
that have the same length. A string swap operation allows you to select any two indices in a string (they can be the same index) and exchange the characters at those positions.
Your task is to determine if you can make the two strings identical by performing at most one swap operation on exactly one of the two strings.
The function should return true
if it's possible to make both strings equal with this constraint, and false
otherwise.
For example:
- If
s1 = "bank"
ands2 = "kanb"
, you can swap indices 0 and 3 ins2
to get"bank"
, making both strings equal. This would returntrue
. - If
s1 = "attack"
ands2 = "defend"
, no single swap can make them equal, so this would returnfalse
. - If
s1 = "kelb"
ands2 = "kelb"
, the strings are already equal (zero swaps needed), so this would returntrue
.
The key insight is that for two strings to be made equal with at most one swap:
- They must either be already identical (0 differences)
- Or have exactly 2 positions where characters differ, and swapping those two characters in one string would make them match the other string
Intuition
When we think about making two strings equal with at most one swap operation on exactly one string, we need to consider what patterns would allow this to happen.
First, let's think about the possible scenarios:
- If the strings are already identical, we need 0 swaps, which satisfies "at most one swap"
- If the strings differ at exactly 2 positions, we might be able to fix them with 1 swap
- If the strings differ at 1 position or more than 2 positions, it's impossible to make them equal with just one swap
The key realization is that when we perform a swap operation, we're changing exactly 2 characters in one string (or 0 if we swap a position with itself). This means a single swap can only fix differences at exactly 2 positions.
For the strings to become equal after one swap, the mismatched characters must be "complementary." If at position i
we have s1[i] = 'a'
and s2[i] = 'b'
, and at position j
we have s1[j] = 'b'
and s2[j] = 'a'
, then swapping positions i
and j
in either string would make them equal.
This leads us to track:
- How many positions have different characters (
cnt
) - What are the mismatched characters at the first difference (
c1
andc2
) - When we encounter the second difference, check if the characters are the "reverse" of the first difference
The elegant part is that we don't need to store all differences - we only care about whether there are 0, 2, or some other number of differences. If there are exactly 2 differences and they're complementary (can be fixed by one swap), we return true
. Otherwise, we return false
.
Solution Approach
The solution uses a counting approach with early termination to efficiently determine if two strings can be made equal with at most one swap.
We initialize:
cnt = 0
: Counter for the number of positions where characters differc1 = None
andc2 = None
: Variables to store the first pair of mismatched characters
We traverse both strings simultaneously using zip(s1, s2)
. For each pair of characters (a, b)
at the same position:
-
When characters differ (
a != b
):- Increment
cnt
by 1 - Check for immediate failure conditions:
- If
cnt > 2
: More than 2 differences means we can't fix with one swap, returnFalse
- If
cnt == 2
and the second mismatch doesn't complement the first:- Check if
a != c2
orb != c1
- This verifies whether swapping would actually fix both mismatches
- If not complementary, return
False
- Check if
- If
- Store the mismatched characters:
c1 = a
andc2 = b
- Increment
-
After traversal completes:
- Return
cnt != 1
- This handles three valid cases:
cnt == 0
: Strings are already equal (no swap needed)cnt == 2
: Exactly two differences that are complementary (validated during traversal)
- And rejects the invalid case:
cnt == 1
: Only one difference can't be fixed with a swap
- Return
The algorithm runs in O(n)
time where n
is the length of the strings, and uses O(1)
extra space. The early termination when cnt > 2
ensures we don't waste time checking the rest of the strings once we know they can't be made equal with one swap.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s1 = "bank"
and s2 = "kanb"
.
Initial State:
cnt = 0
(no differences found yet)c1 = None
,c2 = None
(no mismatched characters stored)
Step-by-step traversal:
-
Position 0:
s1[0] = 'b'
,s2[0] = 'k'
- Characters differ, so
cnt = 1
- Store the mismatch:
c1 = 'b'
,c2 = 'k'
- Continue (only 1 difference so far)
- Characters differ, so
-
Position 1:
s1[1] = 'a'
,s2[1] = 'a'
- Characters match, no action needed
cnt
remains 1
-
Position 2:
s1[2] = 'n'
,s2[2] = 'n'
- Characters match, no action needed
cnt
remains 1
-
Position 3:
s1[3] = 'k'
,s2[3] = 'b'
- Characters differ, so
cnt = 2
- Check if this second mismatch complements the first:
- Current:
s1[3] = 'k'
,s2[3] = 'b'
- First mismatch:
c1 = 'b'
,c2 = 'k'
- Is
s1[3] == c2
? Yes,'k' == 'k'
✓ - Is
s2[3] == c1
? Yes,'b' == 'b'
✓
- Current:
- The mismatches are complementary! If we swap positions 0 and 3 in
s2
, we get:s2
becomes "bank" (swapping 'k' and 'b')
- Continue traversal
- Characters differ, so
Final Check:
cnt = 2
- Return
cnt != 1
→2 != 1
→True
The algorithm correctly identifies that these strings can be made equal with exactly one swap operation (swapping indices 0 and 3 in s2
).
Counter-example: If we had s1 = "abcd"
and s2 = "abdc"
, we'd find differences at positions 2 and 3. However, s1[2] = 'c'
, s2[2] = 'd'
and s1[3] = 'd'
, s2[3] = 'c'
. These are complementary (swapping positions 2 and 3 in either string makes them equal), so the function returns True
.
Solution Implementation
1class Solution:
2 def areAlmostEqual(self, s1: str, s2: str) -> bool:
3 # Count the number of positions where characters differ
4 diff_count = 0
5 # Store the first pair of differing characters
6 first_char_s1 = None
7 first_char_s2 = None
8
9 # Iterate through both strings simultaneously
10 for char_s1, char_s2 in zip(s1, s2):
11 # Check if characters at current position differ
12 if char_s1 != char_s2:
13 diff_count += 1
14
15 # If more than 2 differences, strings can't be made equal with one swap
16 if diff_count > 2:
17 return False
18
19 # On the second difference, check if it forms a valid swap with the first
20 if diff_count == 2:
21 # Valid swap means current chars match the first pair in reverse
22 if char_s1 != first_char_s2 or char_s2 != first_char_s1:
23 return False
24
25 # Store the first differing pair
26 if diff_count == 1:
27 first_char_s1 = char_s1
28 first_char_s2 = char_s2
29
30 # Return True if strings are identical (0 diffs) or exactly one swap needed (2 diffs)
31 # Return False if only 1 difference (can't swap a single position)
32 return diff_count != 1
33
1class Solution {
2 public boolean areAlmostEqual(String s1, String s2) {
3 // Count of positions where characters differ
4 int differenceCount = 0;
5
6 // Store the first pair of differing characters
7 char firstStringChar = 0;
8 char secondStringChar = 0;
9
10 // Iterate through both strings simultaneously
11 for (int i = 0; i < s1.length(); i++) {
12 char currentCharS1 = s1.charAt(i);
13 char currentCharS2 = s2.charAt(i);
14
15 // Check if characters at current position differ
16 if (currentCharS1 != currentCharS2) {
17 differenceCount++;
18
19 // More than 2 differences means we can't make strings equal with one swap
20 if (differenceCount > 2) {
21 return false;
22 }
23
24 // On second difference, check if swapping would make strings equal
25 if (differenceCount == 2) {
26 // The current pair must be the reverse of the first pair
27 if (currentCharS1 != secondStringChar || currentCharS2 != firstStringChar) {
28 return false;
29 }
30 }
31
32 // Store the first differing pair for later comparison
33 if (differenceCount == 1) {
34 firstStringChar = currentCharS1;
35 secondStringChar = currentCharS2;
36 }
37 }
38 }
39
40 // Strings are almost equal if they're identical (0 differences)
41 // or have exactly 2 differences that can be swapped
42 return differenceCount != 1;
43 }
44}
45
1class Solution {
2public:
3 bool areAlmostEqual(string s1, string s2) {
4 int mismatchCount = 0; // Track number of positions where characters differ
5 char firstMismatchS1 = 0; // Store character from s1 at first mismatch position
6 char firstMismatchS2 = 0; // Store character from s2 at first mismatch position
7
8 // Iterate through both strings simultaneously
9 for (int i = 0; i < s1.size(); ++i) {
10 char charFromS1 = s1[i];
11 char charFromS2 = s2[i];
12
13 // Check if characters at current position are different
14 if (charFromS1 != charFromS2) {
15 mismatchCount++;
16
17 // More than 2 mismatches means we can't make strings equal with one swap
18 if (mismatchCount > 2) {
19 return false;
20 }
21
22 // At second mismatch, check if swapping would make strings equal
23 // The characters should be swapped versions of the first mismatch
24 if (mismatchCount == 2) {
25 if (charFromS1 != firstMismatchS2 || charFromS2 != firstMismatchS1) {
26 return false;
27 }
28 }
29
30 // Store the first mismatch characters for later comparison
31 if (mismatchCount == 1) {
32 firstMismatchS1 = charFromS1;
33 firstMismatchS2 = charFromS2;
34 }
35 }
36 }
37
38 // Strings are almost equal if they're identical (0 mismatches)
39 // or have exactly 2 mismatches that can be swapped
40 return mismatchCount != 1;
41 }
42};
43
1/**
2 * Checks if two strings can be made equal by swapping at most one pair of characters
3 * @param s1 - First string to compare
4 * @param s2 - Second string to compare
5 * @returns true if strings are equal or can be made equal with one swap, false otherwise
6 */
7function areAlmostEqual(s1: string, s2: string): boolean {
8 // Store the first pair of mismatched characters
9 let firstMismatchChar1: string | undefined;
10 let firstMismatchChar2: string | undefined;
11
12 // Count the number of positions where characters differ
13 let mismatchCount: number = 0;
14
15 // Iterate through both strings character by character
16 for (let i = 0; i < s1.length; i++) {
17 const charFromS1: string = s1.charAt(i);
18 const charFromS2: string = s2.charAt(i);
19
20 // Check if characters at current position are different
21 if (charFromS1 !== charFromS2) {
22 mismatchCount++;
23
24 // If more than 2 mismatches, strings cannot be made equal with one swap
25 if (mismatchCount > 2) {
26 return false;
27 }
28
29 // On second mismatch, verify if swapping would make strings equal
30 if (mismatchCount === 2) {
31 // Check if the second mismatch forms a valid swap pair with the first
32 if (charFromS1 !== firstMismatchChar2 || charFromS2 !== firstMismatchChar1) {
33 return false;
34 }
35 }
36
37 // Store the first mismatch for later comparison
38 if (mismatchCount === 1) {
39 firstMismatchChar1 = charFromS1;
40 firstMismatchChar2 = charFromS2;
41 }
42 }
43 }
44
45 // Return true if strings are identical (0 mismatches) or have exactly 2 valid swappable mismatches
46 // Return false if there's exactly 1 mismatch (cannot swap a single character)
47 return mismatchCount !== 1;
48}
49
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the strings s1
and s2
. The algorithm iterates through both strings exactly once using the zip
function, performing constant-time operations (comparisons and variable assignments) for each character pair.
The space complexity is O(1)
. The algorithm uses only a fixed amount of extra space regardless of the input size: one counter variable cnt
and two variables c1
and c2
to store characters. These variables do not scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle the Already-Equal Case
A common mistake is focusing only on finding exactly 2 differences and forgetting that strings that are already identical should also return true
.
Incorrect approach:
def areAlmostEqual(self, s1: str, s2: str) -> bool:
# ... finding differences ...
return diff_count == 2 # Wrong! Misses the case where diff_count == 0
Solution: Always check for diff_count == 0
OR diff_count == 2
, which is elegantly handled by return diff_count != 1
.
2. Not Validating the Swap Would Actually Work
Another pitfall is counting exactly 2 differences but not verifying that swapping those two positions would actually make the strings equal.
Incorrect approach:
def areAlmostEqual(self, s1: str, s2: str) -> bool:
diff_count = 0
for char_s1, char_s2 in zip(s1, s2):
if char_s1 != char_s2:
diff_count += 1
return diff_count == 0 or diff_count == 2 # Wrong! Doesn't verify the swap works
For example, with s1 = "ab"
and s2 = "cd"
, this would incorrectly return true
even though swapping can't make them equal.
Solution: Store the first pair of differing characters and verify that the second pair is exactly the reverse (i.e., char_s1 == first_char_s2
and char_s2 == first_char_s1
).
3. Inefficient String Comparison After Performing Swaps
Some solutions actually perform the swap operation and then compare entire strings, which is less efficient.
Inefficient approach:
def areAlmostEqual(self, s1: str, s2: str) -> bool:
if s1 == s2:
return True
for i in range(len(s1)):
for j in range(i, len(s1)):
# Actually perform swap on s1
s1_list = list(s1)
s1_list[i], s1_list[j] = s1_list[j], s1_list[i]
if ''.join(s1_list) == s2:
return True
return False
This has O(n³) time complexity due to nested loops and string comparisons.
Solution: Use the counting approach with early termination as shown in the original solution, achieving O(n) time complexity.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
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!