Facebook Pixel

2840. Check if Strings Can be Made Equal With Operations II

MediumHash TableStringSorting
Leetcode Link

Problem Description

You have two strings s1 and s2, both of the same length n, containing only lowercase English letters.

You can perform the following operation on either string as many times as you want:

  • Select any two indices i and j where i < j
  • The difference j - i must be even (e.g., 2, 4, 6, etc.)
  • Swap the characters at positions i and j in the string

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

The key insight is that you can only swap characters whose indices have the same parity (both even or both odd). For example:

  • Indices 0 and 2 can be swapped (difference = 2, which is even)
  • Indices 1 and 3 can be swapped (difference = 2, which is even)
  • Indices 0 and 4 can be swapped (difference = 4, which is even)
  • But indices 0 and 1 cannot be swapped (difference = 1, which is odd)

This means characters at even positions can only be rearranged among themselves, and characters at odd positions can only be rearranged among themselves. Therefore, for the two strings to be made equal, they must have the same set of characters at even positions and the same set of characters at 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 characters at indices where the difference is even, we need to understand which characters can eventually reach which positions.

Consider indices 0, 1, 2, 3, 4, 5...

  • From index 0, we can swap with indices 2, 4, 6... (differences of 2, 4, 6...)
  • From index 1, we can swap with indices 3, 5, 7... (differences of 2, 4, 6...)
  • From index 2, we can swap with indices 0, 4, 6... (differences of -2, 2, 4...)

Notice a pattern? Characters at even indices (0, 2, 4, ...) can only ever swap with other even indices. Similarly, characters at odd indices (1, 3, 5, ...) can only swap with other odd indices. This is because for any two indices i and j to have an even difference, they must have the same parity (both even or both odd).

This creates two independent groups:

  • Group 1: All characters at even positions (indices 0, 2, 4, ...)
  • Group 2: All characters at odd positions (indices 1, 3, 5, ...)

Within each group, we can rearrange the characters in any order we want through a series of swaps. For example, if we want to move a character from position 0 to position 4, we can do it through intermediate swaps.

Since characters cannot move between these two groups, for two strings to be made equal:

  1. The characters at even positions in s1 must be the same set as the characters at even positions in s2 (though possibly in different order)
  2. The characters at odd positions in s1 must be the same set as the characters at odd positions in s2 (though possibly in different order)

This is why the solution simply checks if the sorted characters at even positions match and the sorted characters at odd positions match between the two strings.

Learn more about Sorting patterns.

Solution Approach

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

Counting Approach (Reference Solution):

We can count the frequency of each character at even and odd positions separately for both strings:

  1. Create frequency counters for characters at even positions in s1 and s2
  2. Create frequency counters for characters at odd positions in s1 and s2
  3. Compare if the frequency distributions match for both groups

This works because if two multisets have the same frequency distribution, they can be rearranged to form the same sequence.

Sorting Approach (Given Code):

The provided solution uses a more concise sorting approach:

sorted(s1[::2]) == sorted(s2[::2]) and sorted(s1[1::2]) == sorted(s2[1::2])

Here's how it works:

  1. Extract even-positioned characters: s1[::2] gets characters at indices 0, 2, 4, ... from s1. Similarly for s2[::2]

  2. Extract odd-positioned characters: s1[1::2] gets characters at indices 1, 3, 5, ... from s1. Similarly for s2[1::2]

  3. Sort and compare: By sorting the characters in each group, we normalize their order. If the sorted sequences are equal, it means both strings have the same multiset of characters at even positions and the same multiset at odd positions.

Time and Space Complexity:

  • Time Complexity: O(n log n) for the sorting approach, where n is the length of the string. The slicing operations take O(n) and sorting takes O(n/2 * log(n/2)) for each group, which simplifies to O(n log n).

  • Space Complexity: O(n) for storing the sliced strings and their sorted versions.

The counting approach mentioned in the reference would have O(n + |Ξ£|) time complexity and O(|Ξ£|) space complexity, where |Ξ£| is the size of the character set (26 for lowercase English letters), making it more efficient for longer strings.

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 an example with s1 = "abcd" and s2 = "cdab".

Step 1: Identify the character groups

For s1 = "abcd":

  • Even positions (indices 0, 2): characters 'a', 'c'
  • Odd positions (indices 1, 3): characters 'b', 'd'

For s2 = "cdab":

  • Even positions (indices 0, 2): characters 'c', 'a'
  • Odd positions (indices 1, 3): characters 'd', 'b'

Step 2: Check if we can rearrange within groups

Even positions:

  • s1 has {'a', 'c'} at even positions
  • s2 has {'c', 'a'} at even positions
  • These are the same multiset! βœ“

Odd positions:

  • s1 has {'b', 'd'} at odd positions
  • s2 has {'d', 'b'} at odd positions
  • These are the same multiset! βœ“

Step 3: Verify with sorting approach

Using the solution code:

  • sorted(s1[::2]) = sorted("ac") = "ac"
  • sorted(s2[::2]) = sorted("ca") = "ac" βœ“
  • sorted(s1[1::2]) = sorted("bd") = "bd"
  • sorted(s2[1::2]) = sorted("db") = "bd" βœ“

Since both conditions are satisfied, we return true.

Step 4: Show how swaps could work

We could transform s1 to s2 through these swaps:

  1. Start with s1 = "abcd"
  2. Swap indices 0 and 2 (difference = 2, even): "abcd" β†’ "cbad"
  3. Swap indices 1 and 3 (difference = 2, even): "cbad" β†’ "cdab"
  4. Result: s2 = "cdab" βœ“

This confirms that the strings can be made equal through valid operations.

Solution Implementation

1class Solution:
2    def checkStrings(self, s1: str, s2: str) -> bool:
3        """
4        Check if two strings can be made equal by swapping characters at even indices
5        with each other and odd indices with each other.
6      
7        Args:
8            s1: First string to compare
9            s2: Second string to compare
10          
11        Returns:
12            True if strings can be made equal through allowed swaps, False otherwise
13        """
14        # Extract characters at even indices (0, 2, 4, ...) from both strings
15        # and check if they contain the same characters (order doesn't matter)
16        even_chars_match = sorted(s1[::2]) == sorted(s2[::2])
17      
18        # Extract characters at odd indices (1, 3, 5, ...) from both strings
19        # and check if they contain the same characters (order doesn't matter)
20        odd_chars_match = sorted(s1[1::2]) == sorted(s2[1::2])
21      
22        # Both even and odd position characters must match for the strings
23        # to be transformable into each other
24        return even_chars_match and odd_chars_match
25
1class Solution {
2    public boolean checkStrings(String s1, String s2) {
3        // Create a 2D array to track character frequency differences
4        // frequencyDiff[0] tracks characters at even indices
5        // frequencyDiff[1] tracks characters at odd indices
6        int[][] frequencyDiff = new int[2][26];
7      
8        // Iterate through both strings simultaneously
9        for (int i = 0; i < s1.length(); i++) {
10            // Determine if current index is even (0) or odd (1)
11            int parityIndex = i & 1;
12          
13            // Get character indices (0-25 for 'a'-'z')
14            int charIndexS1 = s1.charAt(i) - 'a';
15            int charIndexS2 = s2.charAt(i) - 'a';
16          
17            // Increment count for character from s1 at this parity position
18            frequencyDiff[parityIndex][charIndexS1]++;
19          
20            // Decrement count for character from s2 at this parity position
21            frequencyDiff[parityIndex][charIndexS2]--;
22        }
23      
24        // Check if all frequency differences are zero
25        // If zero, characters at even/odd positions match between strings
26        for (int charIndex = 0; charIndex < 26; charIndex++) {
27            // Check even position frequencies
28            if (frequencyDiff[0][charIndex] != 0) {
29                return false;
30            }
31            // Check odd position frequencies
32            if (frequencyDiff[1][charIndex] != 0) {
33                return false;
34            }
35        }
36      
37        // All frequency differences are zero, strings can be made equal
38        return true;
39    }
40}
41
1class Solution {
2public:
3    bool checkStrings(string s1, string s2) {
4        // Create a 2x26 array to track character counts
5        // Row 0: characters at even indices, Row 1: characters at odd indices
6        // Each column represents a letter from 'a' to 'z'
7        vector<vector<int>> charCount(2, vector<int>(26, 0));
8      
9        // Iterate through both strings simultaneously
10        for (int i = 0; i < s1.size(); ++i) {
11            // Determine if current position is even (0) or odd (1)
12            int parityIndex = i & 1;  // Bitwise AND with 1 gives 0 for even, 1 for odd
13          
14            // Increment count for character from s1 at current parity position
15            ++charCount[parityIndex][s1[i] - 'a'];
16          
17            // Decrement count for character from s2 at current parity position
18            --charCount[parityIndex][s2[i] - 'a'];
19        }
20      
21        // Check if all character counts are zero
22        // If they are, it means s1 and s2 have the same characters at even/odd positions
23        for (int i = 0; i < 26; ++i) {
24            // Check both even and odd position counts for each character
25            if (charCount[0][i] != 0 || charCount[1][i] != 0) {
26                return false;
27            }
28        }
29      
30        // All counts are zero, strings can be made equal
31        return true;
32    }
33};
34
1/**
2 * Checks if two strings can be made equal by swapping characters at even/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 checkStrings(s1: string, s2: string): boolean {
8    // Create a 2D array to track character frequency differences
9    // Index 0: tracks characters at even positions
10    // Index 1: tracks characters at odd positions
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 index (0-25) for lowercase letters
22        const s1CharIndex = s1.charCodeAt(i) - 97;
23        const s2CharIndex = s2.charCodeAt(i) - 97;
24      
25        // Increment count for character from s1 at current parity position
26        characterCountDifference[positionParity][s1CharIndex]++;
27      
28        // Decrement count for character from s2 at current parity position
29        characterCountDifference[positionParity][s2CharIndex]--;
30    }
31  
32    // Check if all character counts are balanced (equal to 0)
33    for (let charIndex = 0; charIndex < 26; charIndex++) {
34        // If any character has non-zero count at even or odd positions, strings cannot be made equal
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 slicing operations s1[::2] and s1[1::2] each take O(n) time to create new strings containing characters at even and odd indices respectively
  • The sorted() function is called 4 times total (twice for each string - once for even indices and once for odd indices)
  • Each sorted() call on a string of length n/2 takes O((n/2) log(n/2)) time
  • Since we have 4 sorting operations: 4 * O((n/2) log(n/2)) = O(2n log(n/2)) = O(n log n)
  • The equality comparisons between sorted lists take O(n) time
  • Overall: O(n) + O(n log n) + O(n) = O(n log n)

Space Complexity: O(n)

  • The slicing operations create 4 new strings/subsequences, each of size approximately n/2, requiring O(2n) = O(n) space total
  • The sorted() function creates new sorted lists from these sliced strings, also requiring O(n) space in total for all 4 sorted lists
  • The space used for sorting internally (typically O(n) for Python's Timsort)
  • Overall: O(n) for slicing + O(n) for sorted results = O(n)

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

Common Pitfalls

1. Misunderstanding the Index Difference Requirement

Pitfall: A common misunderstanding is thinking that you can only swap adjacent pairs with even differences (like indices 0 and 2, or 1 and 3). This might lead to incorrectly assuming that characters can only move within small local regions.

Reality: The condition j - i must be even means that any two indices with the same parity can be swapped. For example:

  • Index 0 can swap with index 2, 4, 6, 8... (all even indices)
  • Index 1 can swap with index 3, 5, 7, 9... (all odd indices)

This actually means that all even-indexed characters can be rearranged arbitrarily among themselves, and all odd-indexed characters can be rearranged arbitrarily among themselves.

2. Attempting to Track Individual Swaps

Pitfall: Trying to solve this problem by simulating actual swaps or finding a specific sequence of swaps to transform one string into another.

# Incorrect approach - trying to find specific swaps
def checkStrings(s1, s2):
    s1_list = list(s1)
    for i in range(len(s1)):
        if s1_list[i] != s2[i]:
            # Try to find a valid swap position
            for j in range(i + 2, len(s1), 2):
                if s1_list[j] == s2[i]:
                    s1_list[i], s1_list[j] = s1_list[j], s1_list[i]
                    break
    return ''.join(s1_list) == s2

Solution: Recognize that since characters at even/odd positions can be freely rearranged among themselves, you only need to check if the multisets match, not find the actual transformation sequence.

3. Incorrect Slicing Syntax

Pitfall: Using incorrect Python slicing syntax when trying to extract even and odd positioned characters.

# Common mistakes:
s1[0::2]   # This is correct but sometimes written as s1[::2]
s1[1::2]   # Correct for odd indices
s1[2::2]   # Wrong! This starts from index 2, missing index 0
s1[:2:]    # Wrong! This only gets first 2 characters

Solution: Remember the slicing syntax [start:stop:step]:

  • s1[::2] or s1[0::2] - starts at 0, goes to end, step by 2 (even indices)
  • s1[1::2] - starts at 1, goes to end, step by 2 (odd indices)

4. Forgetting Edge Cases

Pitfall: Not considering edge cases like empty strings or single-character strings.

# This could fail on edge cases
def checkStrings(s1, s2):
    if len(s1) != len(s2):  # Good check
        return False
    # But what if both are empty strings?
    return sorted(s1[::2]) == sorted(s2[::2]) and sorted(s1[1::2]) == sorted(s2[1::2])

Solution: The provided solution actually handles these cases correctly:

  • Empty strings: ""[::2] returns "", and sorted("") returns []
  • Single character: "a"[::2] returns "a", "a"[1::2] returns ""

5. Inefficient Character Comparison

Pitfall: Using inefficient methods to compare character frequencies, such as nested loops or repeated string operations.

# Inefficient approach
def checkStrings(s1, s2):
    even1, even2 = "", ""
    odd1, odd2 = "", ""
    for i in range(len(s1)):
        if i % 2 == 0:
            even1 += s1[i]
            even2 += s2[i]
        else:
            odd1 += s1[i]
            odd2 += s2[i]
    # String concatenation in loop is O(nΒ²) in worst case
    return sorted(even1) == sorted(even2) and sorted(odd1) == sorted(odd2)

Solution: Use Python's efficient slicing operations s[::2] and s[1::2] which are implemented in C and run in O(n) time, or use collections.Counter for frequency counting if you prefer the counting approach.

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

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More