Facebook Pixel

1790. Check if One String Swap Can Make Strings Equal

EasyHash TableStringCounting
Leetcode Link

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" and s2 = "kanb", you can swap indices 0 and 3 in s2 to get "bank", making both strings equal. This would return true.
  • If s1 = "attack" and s2 = "defend", no single swap can make them equal, so this would return false.
  • If s1 = "kelb" and s2 = "kelb", the strings are already equal (zero swaps needed), so this would return true.

The key insight is that for two strings to be made equal with at most one swap:

  1. They must either be already identical (0 differences)
  2. Or have exactly 2 positions where characters differ, and swapping those two characters in one string would make them match the other string
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. How many positions have different characters (cnt)
  2. What are the mismatched characters at the first difference (c1 and c2)
  3. 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 differ
  • c1 = None and c2 = 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:

  1. 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, return False
      • If cnt == 2 and the second mismatch doesn't complement the first:
        • Check if a != c2 or b != c1
        • This verifies whether swapping would actually fix both mismatches
        • If not complementary, return False
    • Store the mismatched characters: c1 = a and c2 = b
  2. 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

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 Evaluator

Example 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:

  1. 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)
  2. Position 1: s1[1] = 'a', s2[1] = 'a'

    • Characters match, no action needed
    • cnt remains 1
  3. Position 2: s1[2] = 'n', s2[2] = 'n'

    • Characters match, no action needed
    • cnt remains 1
  4. 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'
    • The mismatches are complementary! If we swap positions 0 and 3 in s2, we get:
      • s2 becomes "bank" (swapping 'k' and 'b')
    • Continue traversal

Final Check:

  • cnt = 2
  • Return cnt != 12 != 1True

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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More