A clear explanation of validating string transformation using two pointers and movement constraints.
Problem Restatement
We are given two strings, start and result.
Both strings contain only three characters:
| Character | Meaning |
|---|---|
L | A piece that can move left |
R | A piece that can move right |
X | An empty space |
We can make two kinds of moves:
XL -> LX
RX -> XRThe first move lets L move one step left.
The second move lets R move one step right.
Return True if we can transform start into result. Otherwise, return False.
Input and Output
| Item | Meaning |
|---|---|
| Input | Two strings start and result |
| Output | True if the transformation is possible, otherwise False |
| Characters | Only L, R, and X |
| Length | start.length == result.length |
Function shape:
class Solution:
def canTransform(self, start: str, result: str) -> bool:
...Examples
Example 1:
start = "RXXLRXRXL"
result = "XRLXXRRLX"This returns:
TrueOne valid sequence is:
RXXLRXRXL
XRXLRXRXL
XRLXRXRXL
XRLXXRRXL
XRLXXRRLXExample 2:
start = "X"
result = "L"This returns:
FalseThe characters are different. We cannot create a new L.
Example 3:
start = "LXXLXRLXXL"
result = "XLLXRXLXLX"This returns:
FalseAt least one L would need to move right, which is not allowed.
First Thought: Simulate Moves
One possible idea is to simulate all moves.
From start, we can repeatedly replace:
XL -> LX
RX -> XRThen we check whether we can eventually reach result.
This is difficult because there may be many possible move orders. Trying all possibilities can grow very quickly.
Instead of generating transformations, we can validate whether the final arrangement is possible.
Problem With Simulation
The moves have simple direction rules:
| Move | Effect |
|---|---|
XL -> LX | L moves left |
RX -> XR | R moves right |
So:
| Character | Can move |
|---|---|
L | Left only |
R | Right only |
Also, L and R cannot pass through each other. They only move by swapping with X.
That means if we remove all X characters, the order of L and R must stay the same.
For example:
start = "RXL"
result = "LXR"Ignoring X gives:
start -> "RL"
result -> "LR"The order changed, so the transformation is impossible.
Key Insight
There are two conditions.
First, after removing all X, both strings must have the same sequence of L and R.
start.replace("X", "") == result.replace("X", "")Second, each piece must move only in its allowed direction.
For matching non-X characters:
| Character | Start index | Result index | Valid when |
|---|---|---|---|
L | i | j | i >= j |
R | i | j | i <= j |
Why?
L can only move left, so its final index cannot be greater than its start index.
R can only move right, so its final index cannot be less than its start index.
Algorithm
Use two pointers.
i scans start.
j scans result.
At each step:
- Move
iforward whilestart[i] == "X". - Move
jforward whileresult[j] == "X". - If both pointers reached the end, return
True. - If only one pointer reached the end, return
False. - If
start[i] != result[j], returnFalse. - If the character is
L, requirei >= j. - If the character is
R, requirei <= j. - Move both pointers forward.
Correctness
The algorithm compares only the meaningful pieces, L and R.
Skipping X is valid because X represents empty space. The pieces that matter are the ordered sequence of L and R.
If the algorithm finds different non-X characters at matching positions, then the relative order of pieces differs between start and result. Since pieces cannot pass through each other, no valid move sequence can transform one string into the other.
If the matching piece is L, the algorithm checks that its start index is greater than or equal to its result index. This is necessary because L can only move left through moves of the form XL -> LX.
If the matching piece is R, the algorithm checks that its start index is less than or equal to its result index. This is necessary because R can only move right through moves of the form RX -> XR.
If every piece has the same relative order and every piece moves only in its allowed direction, then the required transformation is possible. Each L can be moved left through available X positions, and each R can be moved right through available X positions without crossing another non-X piece.
Therefore, the algorithm returns True exactly when the transformation is possible.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each pointer moves from left to right once |
| Space | O(1) | Only two indices are stored |
Here, n is the length of the strings.
Implementation
class Solution:
def canTransform(self, start: str, result: str) -> bool:
n = len(start)
i = 0
j = 0
while i < n or j < n:
while i < n and start[i] == "X":
i += 1
while j < n and result[j] == "X":
j += 1
if i == n and j == n:
return True
if i == n or j == n:
return False
if start[i] != result[j]:
return False
if start[i] == "L" and i < j:
return False
if start[i] == "R" and i > j:
return False
i += 1
j += 1
return TrueCode Explanation
We keep two pointers:
i = 0
j = 0Pointer i scans start.
Pointer j scans result.
We skip empty spaces in both strings:
while i < n and start[i] == "X":
i += 1
while j < n and result[j] == "X":
j += 1After skipping X, both pointers should either reach the end together or point to matching pieces.
If both reached the end, all pieces were matched:
if i == n and j == n:
return TrueIf only one reached the end, one string has extra L or R pieces:
if i == n or j == n:
return FalseThe next non-X pieces must match:
if start[i] != result[j]:
return FalseThen we enforce movement direction.
An L cannot move right:
if start[i] == "L" and i < j:
return FalseAn R cannot move left:
if start[i] == "R" and i > j:
return FalseIf all checks pass, we move to the next piece:
i += 1
j += 1Testing
def run_tests():
s = Solution()
assert s.canTransform("RXXLRXRXL", "XRLXXRRLX") is True
assert s.canTransform("X", "L") is False
assert s.canTransform("LXXLXRLXXL", "XLLXRXLXLX") is False
assert s.canTransform("XL", "LX") is True
assert s.canTransform("LX", "XL") is False
assert s.canTransform("RX", "XR") is True
assert s.canTransform("XR", "RX") is False
assert s.canTransform("XXRXXLXXXX", "XXXXRXXLXX") is True
assert s.canTransform("RXL", "LXR") is False
print("all tests passed")
run_tests()Test coverage:
| Test | Why |
|---|---|
"RXXLRXRXL" to "XRLXXRRLX" | Standard valid transformation |
"X" to "L" | Cannot create a new piece |
| Complex invalid case | Checks direction constraints |
"XL" to "LX" | Single valid L move |
"LX" to "XL" | Invalid L moving right |
"RX" to "XR" | Single valid R move |
"XR" to "RX" | Invalid R moving left |
"RXL" to "LXR" | Detects changed L/R order |