LeetCode 2839 - Check if Strings Can be Made Equal With Operations I
The problem gives us two strings, s1 and s2, each containing exactly four lowercase English letters. We are allowed to repeatedly perform a very specific swap operation on either string.
Difficulty: 🟢 Easy
Topics: String
Solution
Problem Understanding
The problem gives us two strings, s1 and s2, each containing exactly four lowercase English letters. We are allowed to repeatedly perform a very specific swap operation on either string. In one operation, we may choose two indices i and j such that j - i = 2, then swap the characters at those positions.
Because the strings are length 4, the only valid swaps are:
- Swap indices
0and2 - Swap indices
1and3
The goal is to determine whether it is possible to transform one string into the other using these operations any number of times.
The key observation is that the operation never allows characters to move between even and odd indices. Characters at indices 0 and 2 can only swap with each other, and characters at indices 1 and 3 can only swap with each other.
This means:
- The characters originally located at even positions must match between the two strings, ignoring order.
- The characters originally located at odd positions must also match between the two strings, ignoring order.
The constraints are extremely small since both strings always have length 4. This means even brute-force solutions are feasible, but the problem is really testing whether we can identify the structural property created by the allowed swap operation.
An important detail is that repeated swaps are allowed. Since swapping the same pair multiple times can rearrange the two positions freely, the order inside each parity group does not matter. However, characters can never cross between parity groups.
Some edge cases that could trip up a naive implementation include repeated characters, strings that are already equal, and cases where the same characters exist overall but are distributed differently across even and odd positions.
Approaches
Brute Force Approach
A brute-force solution would try every reachable configuration of the string by repeatedly applying the allowed swaps. Since there are only two possible swaps, we can generate all reachable states using breadth-first search or depth-first search.
Starting from s1, we repeatedly:
- Swap indices
(0, 2) - Swap indices
(1, 3)
We store all visited strings to avoid infinite loops. If we ever reach s2, we return true.
This approach is correct because it explicitly explores every possible string reachable through the allowed operations.
Although the constraints are tiny and this works fine here, it is unnecessarily complicated for such a structured problem. The main weakness is that it treats the problem as a generic graph search instead of exploiting the mathematical property of the swaps.
Optimal Approach
The better solution comes from observing how the swaps behave.
The allowed operations only swap characters within the same parity:
- Even indices:
0and2 - Odd indices:
1and3
Therefore:
- Characters at even positions can be rearranged freely among even positions.
- Characters at odd positions can be rearranged freely among odd positions.
- Even-position characters can never move to odd positions, and vice versa.
So the problem reduces to checking whether:
- The multiset of even-index characters in
s1matches the multiset of even-index characters ins2 - The multiset of odd-index characters in
s1matches the multiset of odd-index characters ins2
We can simply sort the two even-character pairs and the two odd-character pairs, then compare them.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(1) | O(1) | Explores all reachable states using BFS or DFS |
| Optimal | O(1) | O(1) | Compares even and odd index character groups |
Algorithm Walkthrough
- Extract the characters at even indices from
s1.
These are the characters at indices 0 and 2. Since only even positions can swap with each other, these characters form one independent group.
2. Extract the characters at odd indices from s1.
These are the characters at indices 1 and 3. They form the second independent group.
3. Repeat the same extraction for s2.
We now have four groups:
- Even characters from
s1 - Odd characters from
s1 - Even characters from
s2 - Odd characters from
s2
- Sort each group.
Sorting removes the effect of ordering. Since swaps allow arbitrary rearrangement inside each parity group, only the character counts matter. 5. Compare the sorted even groups and sorted odd groups.
If both pairs match, then the transformation is possible. Otherwise, it is impossible. 6. Return the result of the comparison.
Why it works
The operation preserves index parity. A character starting at an even index can only ever appear at an even index, and similarly for odd indices. Therefore, the even-index character multiset and odd-index character multiset are invariants of the process.
If these invariants match between the two strings, we can rearrange characters within each parity group to obtain the target string. If they do not match, no sequence of allowed swaps can ever produce the target.
Python Solution
class Solution:
def canBeEqual(self, s1: str, s2: str) -> bool:
even_s1 = sorted([s1[0], s1[2]])
odd_s1 = sorted([s1[1], s1[3]])
even_s2 = sorted([s2[0], s2[2]])
odd_s2 = sorted([s2[1], s2[3]])
return even_s1 == even_s2 and odd_s1 == odd_s2
The implementation directly follows the parity-group observation.
First, we collect the even-index characters from both strings. These are the only characters that can swap with each other. Then we do the same for the odd-index characters.
We sort each pair so that order no longer matters. For example, ['a', 'c'] and ['c', 'a'] become identical after sorting.
Finally, we compare the even groups and odd groups separately. Both must match for the transformation to be possible.
Because the string length is fixed at four, this implementation runs in constant time and uses constant extra space.
Go Solution
import "sort"
func canBeEqual(s1 string, s2 string) bool {
evenS1 := []byte{s1[0], s1[2]}
oddS1 := []byte{s1[1], s1[3]}
evenS2 := []byte{s2[0], s2[2]}
oddS2 := []byte{s2[1], s2[3]}
sort.Slice(evenS1, func(i, j int) bool {
return evenS1[i] < evenS1[j]
})
sort.Slice(oddS1, func(i, j int) bool {
return oddS1[i] < oddS1[j]
})
sort.Slice(evenS2, func(i, j int) bool {
return evenS2[i] < evenS2[j]
})
sort.Slice(oddS2, func(i, j int) bool {
return oddS2[i] < oddS2[j]
})
return string(evenS1) == string(evenS2) &&
string(oddS1) == string(oddS2)
}
The Go implementation follows the same logic as the Python version.
Since Go strings are immutable, we extract the relevant characters into byte slices before sorting them. We use sort.Slice to sort each two-character slice independently.
There are no concerns about integer overflow or large memory usage because the input size is fixed and extremely small.
Worked Examples
Example 1
Input:
s1 = "abcd"
s2 = "cdab"
Step 1: Extract parity groups
| String | Even Indices (0,2) | Odd Indices (1,3) |
|---|---|---|
| s1 | ['a', 'c'] | ['b', 'd'] |
| s2 | ['c', 'a'] | ['d', 'b'] |
Step 2: Sort groups
| String | Sorted Even | Sorted Odd |
|---|---|---|
| s1 | ['a', 'c'] | ['b', 'd'] |
| s2 | ['a', 'c'] | ['b', 'd'] |
Step 3: Compare
Both even groups match and both odd groups match.
Result:
true
Example 2
Input:
s1 = "abcd"
s2 = "dacb"
Step 1: Extract parity groups
| String | Even Indices (0,2) | Odd Indices (1,3) |
|---|---|---|
| s1 | ['a', 'c'] | ['b', 'd'] |
| s2 | ['d', 'c'] | ['a', 'b'] |
Step 2: Sort groups
| String | Sorted Even | Sorted Odd |
|---|---|---|
| s1 | ['a', 'c'] | ['b', 'd'] |
| s2 | ['c', 'd'] | ['a', 'b'] |
Step 3: Compare
The even groups do not match.
Result:
false
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) | Only a fixed number of characters are processed |
| Space | O(1) | Uses a constant amount of extra storage |
The algorithm operates on strings of length exactly four, so the amount of work never grows with input size. Even though sorting is used, the sorted arrays always contain only two elements, making the runtime effectively constant.
Test Cases
solution = Solution()
assert solution.canBeEqual("abcd", "cdab") is True # provided example, valid swaps
assert solution.canBeEqual("abcd", "dacb") is False # provided example, parity mismatch
assert solution.canBeEqual("abcd", "abcd") is True # already equal
assert solution.canBeEqual("abdc", "acbd") is False # impossible parity movement
assert solution.canBeEqual("zzzz", "zzzz") is True # all characters identical
assert solution.canBeEqual("abab", "bbaa") is False # same total chars, wrong parity groups
assert solution.canBeEqual("aabb", "bbaa") is True # parity groups match after swaps
assert solution.canBeEqual("xyzw", "xwzy") is True # odd positions swapped
assert solution.canBeEqual("xyzw", "zyxw") is True # even positions swapped
assert solution.canBeEqual("xyzw", "wxyz") is False # parity groups incompatible
Test Summary
| Test | Why |
|---|---|
"abcd", "cdab" |
Valid transformation using both swaps |
"abcd", "dacb" |
Impossible due to parity mismatch |
"abcd", "abcd" |
Already equal strings |
"abdc", "acbd" |
Characters cannot cross parity groups |
"zzzz", "zzzz" |
Repeated identical characters |
"abab", "bbaa" |
Same letters overall, invalid parity distribution |
"aabb", "bbaa" |
Duplicate letters with valid parity grouping |
"xyzw", "xwzy" |
Odd-index swap only |
"xyzw", "zyxw" |
Even-index swap only |
"xyzw", "wxyz" |
Both parity groups invalid |
Edge Cases
One important edge case is when the strings are already equal. A careless implementation might attempt unnecessary transformations or incorrectly assume at least one swap is required. Our solution handles this naturally because both parity groups already match.
Another important case involves repeated characters. For example, "aabb" and "bbaa" can still be transformed into each other because the even-index groups and odd-index groups contain the same characters after sorting. The algorithm correctly treats parity groups as multisets rather than ordered sequences.
A third edge case occurs when the strings contain the same overall characters but distributed across different parity positions. For example, "abab" and "bbaa" contain identical character counts globally, but characters would need to move between even and odd indices to complete the transformation. Since the allowed operations never permit parity changes, the algorithm correctly returns false.
A subtle case is when only one parity group matches. For example, the even-index characters may align perfectly while the odd-index characters differ. The implementation independently checks both groups, ensuring partial matches do not incorrectly produce a positive result.