2840. Check if Strings Can be Made Equal With Operations II
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
andj
wherei < j
- The difference
j - i
must be even (e.g., 2, 4, 6, etc.) - Swap the characters at positions
i
andj
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.
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:
- The characters at even positions in
s1
must be the same set as the characters at even positions ins2
(though possibly in different order) - The characters at odd positions in
s1
must be the same set as the characters at odd positions ins2
(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:
- Create frequency counters for characters at even positions in
s1
ands2
- Create frequency counters for characters at odd positions in
s1
ands2
- 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:
-
Extract even-positioned characters:
s1[::2]
gets characters at indices 0, 2, 4, ... froms1
. Similarly fors2[::2]
-
Extract odd-positioned characters:
s1[1::2]
gets characters at indices 1, 3, 5, ... froms1
. Similarly fors2[1::2]
-
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, wheren
is the length of the string. The slicing operations takeO(n)
and sorting takesO(n/2 * log(n/2))
for each group, which simplifies toO(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 EvaluatorExample 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 positionss2
has {'c', 'a'} at even positions- These are the same multiset! β
Odd positions:
s1
has {'b', 'd'} at odd positionss2
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:
- Start with
s1 = "abcd"
- Swap indices 0 and 2 (difference = 2, even): "abcd" β "cbad"
- Swap indices 1 and 3 (difference = 2, even): "cbad" β "cdab"
- 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]
ands1[1::2]
each takeO(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 lengthn/2
takesO((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
, requiringO(2n) = O(n)
space total - The
sorted()
function creates new sorted lists from these sliced strings, also requiringO(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]
ors1[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""
, andsorted("")
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.
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Donβt Miss This!