Facebook Pixel

2839. Check if Strings Can be Made Equal With Operations I

EasyString
Leetcode Link

Problem Description

You have two strings s1 and s2, each containing exactly 4 lowercase English letters.

You can perform a specific swap operation on either string as many times as you want:

  • Pick two indices i and j where j - i = 2 (meaning the indices are exactly 2 positions apart)
  • Swap the characters at positions i and j in the string

For example, in a string of length 4 with indices 0, 1, 2, 3:

  • You can swap characters at indices 0 and 2
  • You can swap characters at indices 1 and 3

Your task is to determine if it's possible to make s1 equal to s2 by applying this swap operation any number of times to either string. Return true if you can make them equal, false otherwise.

The key insight is that this operation only allows swapping between positions that are 2 apart. This means:

  • Characters at even positions (0, 2) can only be swapped with each other
  • Characters at odd positions (1, 3) can only be swapped with each other
  • Characters at even positions can never move to odd positions and vice versa

Therefore, for the strings to be made equal, the characters at even positions in s1 must match (in some order) the characters at even positions in s2, and similarly for odd positions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about what the operation actually allows us to do. When we can only swap indices that are exactly 2 positions apart, we need to identify which positions can interact with each other.

In a string of length 4 with indices [0, 1, 2, 3]:

  • Index 0 can swap with index 2
  • Index 1 can swap with index 3
  • That's it - no other swaps are possible

This creates two independent groups:

  • Group 1: positions 0 and 2 (even indices)
  • Group 2: positions 1 and 3 (odd indices)

Characters within the same group can be rearranged freely through swapping, but characters can never move between groups. A character at position 0 can move to position 2 and vice versa, but it can never reach positions 1 or 3.

This means the problem reduces to checking if:

  1. The characters at even positions in s1 (positions 0 and 2) contain the same set of characters as the even positions in s2
  2. The characters at odd positions in s1 (positions 1 and 3) contain the same set of characters as the odd positions in s2

If both conditions are met, we can rearrange the characters within each group to match the target string. If either condition fails, it's impossible to make the strings equal.

For example:

  • s1 = "abcd" and s2 = "cdab"
  • Even positions: s1[0,2] = "ac", s2[0,2] = "ca" → same characters ✓
  • Odd positions: s1[1,3] = "bd", s2[1,3] = "db" → same characters ✓
  • Result: Can be made equal

The solution simply sorts the characters at even and odd positions separately for both strings and compares them. If they match, the transformation is possible.

Solution Approach

Based on our intuition that characters at even and odd positions form independent groups, we can implement the solution using a simple counting or sorting approach.

The implementation leverages Python's string slicing to separate characters by position parity:

  • s1[::2] extracts characters at even indices (0, 2)
  • s1[1::2] extracts characters at odd indices (1, 3)

Algorithm Steps:

  1. Extract even-indexed characters from both strings:

    • For s1: Get characters at positions 0 and 2 using s1[::2]
    • For s2: Get characters at positions 0 and 2 using s2[::2]
  2. Extract odd-indexed characters from both strings:

    • For s1: Get characters at positions 1 and 3 using s1[1::2]
    • For s2: Get characters at positions 1 and 3 using s2[1::2]
  3. Sort and compare the extracted characters:

    • Sort the even-indexed characters from both strings and check if they're equal
    • Sort the odd-indexed characters from both strings and check if they're equal
  4. Return the result:

    • Return True if both comparisons are equal
    • Return False otherwise

Why sorting works: Sorting gives us a canonical representation of the character set at each position group. If two sorted sequences are equal, it means they contain the same characters with the same frequencies, which is exactly what we need to verify.

Time Complexity: O(n + |Σ|) where n is the length of the string (4 in this case) and |Σ| is the size of the character set. Since the string length is fixed at 4, this is effectively O(1).

Space Complexity: O(|Σ|) for storing the sorted characters, which is also effectively O(1) given the fixed string length.

The solution elegantly handles the problem by recognizing that the swap operation partitions the string indices into two independent sets, reducing the problem to a simple character frequency comparison within each set.

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 = "abdc" and s2 = "cbad".

Step 1: Identify what swaps are possible

  • In position indices [0, 1, 2, 3]:
    • We can swap index 0 with 2 (distance = 2)
    • We can swap index 1 with 3 (distance = 2)

Step 2: Extract characters by position groups

For s1 = "abdc":

  • Even positions (0, 2): s1[0] = 'a', s1[2] = 'd' → "ad"
  • Odd positions (1, 3): s1[1] = 'b', s1[3] = 'c' → "bc"

For s2 = "cbad":

  • Even positions (0, 2): s2[0] = 'c', s2[2] = 'a' → "ca"
  • Odd positions (1, 3): s2[1] = 'b', s2[3] = 'd' → "bd"

Step 3: Sort each group and compare

Even positions:

  • s1 even sorted: "ad" → "ad"
  • s2 even sorted: "ca" → "ac"
  • Compare: "ad" ≠ "ac" ❌

Since the even positions don't match, we can already conclude it's impossible to transform s1 into s2. The character 'd' at an even position in s1 cannot be moved to an odd position, and there's no 'd' at any even position in s2.

Result: Return false


Let's verify with a positive example: s1 = "abcd" and s2 = "cdab"

Even positions:

  • s1: positions 0,2 → "ac"
  • s2: positions 0,2 → "ca"
  • Sorted: "ac" = "ac" ✓

Odd positions:

  • s1: positions 1,3 → "bd"
  • s2: positions 1,3 → "db"
  • Sorted: "bd" = "bd" ✓

Both groups match, so we can transform s1 to s2 through swaps:

  1. Start with "abcd"
  2. Swap positions 0 and 2: "cbad"
  3. Swap positions 1 and 3: "cdab" ✓

Result: Return true

Solution Implementation

1class Solution:
2    def canBeEqual(self, s1: str, s2: str) -> bool:
3        """
4        Check if two strings can be made equal by swapping characters at even or odd positions.
5      
6        The key insight is that characters at even indices can only be swapped with other 
7        characters at even indices, and characters at odd indices can only be swapped with 
8        other characters at odd indices.
9      
10        Args:
11            s1: First string to compare
12            s2: Second string to compare
13          
14        Returns:
15            True if the strings can be made equal through allowed swaps, False otherwise
16        """
17        # Extract and sort characters at even positions (0, 2, 4, ...) from both strings
18        s1_even_chars_sorted = sorted(s1[::2])
19        s2_even_chars_sorted = sorted(s2[::2])
20      
21        # Extract and sort characters at odd positions (1, 3, 5, ...) from both strings
22        s1_odd_chars_sorted = sorted(s1[1::2])
23        s2_odd_chars_sorted = sorted(s2[1::2])
24      
25        # Both even and odd position characters must match after sorting
26        return (s1_even_chars_sorted == s2_even_chars_sorted and 
27                s1_odd_chars_sorted == s2_odd_chars_sorted)
28
1class Solution {
2    public boolean canBeEqual(String s1, String s2) {
3        // Create a 2D array to track character frequency differences
4        // First dimension: [0] for even positions, [1] for odd positions  
5        // Second dimension: 26 slots for each lowercase letter (a-z)
6        int[][] characterCountDifference = new int[2][26];
7      
8        // Process each character position in both strings
9        for (int position = 0; position < s1.length(); position++) {
10            // Determine if position is even (0) or odd (1) using bitwise AND
11            int parityIndex = position & 1;
12          
13            // Get the character index (0-25) for the current character in s1
14            int s1CharIndex = s1.charAt(position) - 'a';
15            // Increment count for this character at this parity position
16            characterCountDifference[parityIndex][s1CharIndex]++;
17          
18            // Get the character index (0-25) for the current character in s2
19            int s2CharIndex = s2.charAt(position) - 'a';
20            // Decrement count for this character at this parity position
21            characterCountDifference[parityIndex][s2CharIndex]--;
22        }
23      
24        // Check if all character counts are balanced (equal to zero)
25        // This means each parity position has the same characters in both strings
26        for (int charIndex = 0; charIndex < 26; charIndex++) {
27            // Check even position counts
28            if (characterCountDifference[0][charIndex] != 0) {
29                return false;
30            }
31            // Check odd position counts
32            if (characterCountDifference[1][charIndex] != 0) {
33                return false;
34            }
35        }
36      
37        // All character counts are balanced, strings can be made equal
38        return true;
39    }
40}
41
1class Solution {
2public:
3    bool canBeEqual(string s1, string s2) {
4        // Create a 2D array to track character frequency differences
5        // charFrequency[0] tracks characters at even positions (0, 2, 4, ...)
6        // charFrequency[1] tracks characters at odd positions (1, 3, 5, ...)
7        vector<vector<int>> charFrequency(2, vector<int>(26, 0));
8      
9        // Process each character in both strings
10        for (int i = 0; i < s1.size(); ++i) {
11            int positionParity = i & 1;  // 0 for even indices, 1 for odd indices
12          
13            // Increment count for character from s1 at this position parity
14            ++charFrequency[positionParity][s1[i] - 'a'];
15          
16            // Decrement count for character from s2 at this position parity
17            --charFrequency[positionParity][s2[i] - 'a'];
18        }
19      
20        // Check if all character frequencies are balanced (should be 0)
21        // If any frequency is non-zero, strings cannot be made equal
22        for (int charIndex = 0; charIndex < 26; ++charIndex) {
23            if (charFrequency[0][charIndex] != 0 || charFrequency[1][charIndex] != 0) {
24                return false;
25            }
26        }
27      
28        // All frequencies are balanced, strings can be made equal
29        return true;
30    }
31};
32
1/**
2 * Checks if two strings can be made equal by swapping characters at even or odd positions
3 * @param s1 - First string to compare
4 * @param s2 - Second string to compare
5 * @returns true if strings can be made equal, false otherwise
6 */
7function canBeEqual(s1: string, s2: string): boolean {
8    // Create a 2x26 array to track character frequency differences
9    // Row 0: characters at even positions, Row 1: characters at odd positions
10    // Each column represents a letter (a-z)
11    const characterCountDifference: number[][] = Array.from(
12        { length: 2 }, 
13        () => Array.from({ length: 26 }, () => 0)
14    );
15  
16    // Iterate through both strings simultaneously
17    for (let i = 0; i < s1.length; i++) {
18        // Determine if current position is even (0) or odd (1)
19        const positionParity = i & 1;
20      
21        // Get character codes and normalize to 0-25 range (a=0, b=1, ..., z=25)
22        const s1CharIndex = s1.charCodeAt(i) - 97;
23        const s2CharIndex = s2.charCodeAt(i) - 97;
24      
25        // Increment count for s1 character at this position parity
26        characterCountDifference[positionParity][s1CharIndex]++;
27      
28        // Decrement count for s2 character at this position parity
29        characterCountDifference[positionParity][s2CharIndex]--;
30    }
31  
32    // Check if all character counts are balanced (equal to 0)
33    // If any count is non-zero, strings cannot be made equal
34    for (let charIndex = 0; charIndex < 26; charIndex++) {
35        if (characterCountDifference[0][charIndex] !== 0 || 
36            characterCountDifference[1][charIndex] !== 0) {
37            return false;
38        }
39    }
40  
41    // All character counts are balanced, strings can be made equal
42    return true;
43}
44

Time and Space Complexity

Time Complexity: O(n log n) where n is the length of the input strings.

The code performs the following operations:

  • s1[::2] and s2[::2] extract characters at even indices - O(n/2) each
  • s1[1::2] and s2[1::2] extract characters at odd indices - O(n/2) each
  • sorted() is called 4 times on strings of length n/2 - each sorting takes O((n/2) log(n/2))
  • Total sorting time: 4 * O((n/2) log(n/2)) = O(n log n)
  • The equality comparisons take O(n/2) each

Overall time complexity is dominated by the sorting operations: O(n log n)

Space Complexity: O(n)

The space usage includes:

  • String slicing creates 4 new strings of length n/2 each - O(2n) = O(n)
  • The sorted() function creates 4 new sorted lists/strings of length n/2 each - O(2n) = O(n)
  • The sorting algorithm itself uses O(n/2) auxiliary space for each call

Total space complexity: O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Misunderstanding the Swap Operation Range

The Mistake: Developers often misinterpret "j - i = 2" as allowing any swap where indices differ by 2, leading to incorrect implementations like:

# WRONG: This assumes we can swap any positions 2 apart in any string length
def canBeEqual(self, s1: str, s2: str) -> bool:
    # Might try to generalize for any string length
    for i in range(len(s1) - 2):
        # Attempts to swap positions i and i+2
        pass

Why It's Wrong: The problem specifically states the strings have exactly 4 characters. The constraint "j - i = 2" with a 4-character string means:

  • Only positions (0,2) can be swapped together
  • Only positions (1,3) can be swapped together
  • This creates two independent groups that never mix

The Fix: Recognize that for 4-character strings, the positions form two fixed groups:

# Correct understanding: positions are partitioned into [0,2] and [1,3]
even_positions = s1[::2]   # Gets characters at indices 0, 2
odd_positions = s1[1::2]   # Gets characters at indices 1, 3

Pitfall 2: Attempting Direct String Manipulation

The Mistake: Some developers try to actually perform the swaps to transform one string into another:

# WRONG: Trying to simulate the actual swapping process
def canBeEqual(self, s1: str, s2: str) -> bool:
    s1_list = list(s1)
    # Try swapping positions 0 and 2
    s1_list[0], s1_list[2] = s1_list[2], s1_list[0]
    if ''.join(s1_list) == s2:
        return True
    # Try swapping positions 1 and 3
    s1_list[1], s1_list[3] = s1_list[3], s1_list[1]
    # ... more swap attempts

Why It's Wrong: This approach:

  • Only checks specific swap sequences, not all possible combinations
  • Becomes exponentially complex as you need to check all possible swap combinations
  • Misses the mathematical insight that only character frequencies matter within each group

The Fix: Use sorting or counting to compare character sets instead of simulating swaps:

# Correct: Compare sorted character groups
return (sorted(s1[::2]) == sorted(s2[::2]) and 
        sorted(s1[1::2]) == sorted(s2[1::2]))

Pitfall 3: Overlooking the Independence of Position Groups

The Mistake: Checking if the entire strings contain the same characters without considering position constraints:

# WRONG: Just checking if strings are anagrams
def canBeEqual(self, s1: str, s2: str) -> bool:
    return sorted(s1) == sorted(s2)

Why It's Wrong: This ignores the fundamental constraint that characters at even positions can never move to odd positions and vice versa. For example:

  • s1 = "abcd" and s2 = "badc"
  • These are anagrams, but cannot be made equal through allowed swaps
  • 'a' is at position 0 (even) in s1 but at position 1 (odd) in s2

The Fix: Always separate and compare the position groups independently:

# Correct: Check even and odd positions separately
even_match = sorted(s1[::2]) == sorted(s2[::2])
odd_match = sorted(s1[1::2]) == sorted(s2[1::2])
return even_match and odd_match
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

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

Load More