Facebook Pixel

777. Swap Adjacent in LR String

Problem Description

You are given two strings start and end that consist only of the characters 'L', 'R', and 'X'. You need to determine if you can transform the start string into the end string using a sequence of valid moves.

The valid moves are:

  • Replace "XL" with "LX" (moving an L to the left)
  • Replace "RX" with "XR" (moving an R to the right)

For example, given start = "RXXLRXRXL", you can apply moves to potentially transform it into another valid configuration.

The key insight is that:

  • L characters can only move left (by swapping with X on their left)
  • R characters can only move right (by swapping with X on their right)
  • The relative order of L and R characters cannot change
  • X characters act as empty spaces that allow L and R to move

The solution checks if transformation is possible by:

  1. Skipping all X characters in both strings to compare only the L and R positions
  2. Ensuring the sequence of L and R characters is identical in both strings
  3. Verifying that each L in start is at a position greater than or equal to its corresponding position in end (since L can only move left)
  4. Verifying that each R in start is at a position less than or equal to its corresponding position in end (since R can only move right)

The function returns True if the transformation is possible, False otherwise.

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

Intuition

The first observation is understanding what these moves actually do. When we replace "XL" with "LX", we're essentially moving an L one position to the left. Similarly, replacing "RX" with "XR" moves an R one position to the right. The X characters act like empty spaces that allow movement.

Think of it like a sliding puzzle where L pieces can only slide left, R pieces can only slide right, and X represents empty slots. This immediately tells us something crucial: the relative order of L and R characters can never change! An L can never move past an R, and an R can never move past an L, because they move in opposite directions.

This leads to our first key insight: if we remove all X characters from both strings, the resulting sequences of L and R must be identical. If they're not, transformation is impossible.

The second insight comes from the movement constraints:

  • Since L can only move left, if an L appears at position i in start and needs to be at position j in end, then i >= j must be true
  • Since R can only move right, if an R appears at position i in start and needs to be at position j in end, then i <= j must be true

This naturally leads to a two-pointer approach: we can iterate through both strings simultaneously, skipping X characters, and check:

  1. Do the non-X characters match in order?
  2. Can each L reach its target position (by moving left)?
  3. Can each R reach its target position (by moving right)?

If all these conditions are met, the transformation is possible. The beauty of this solution is that we don't need to simulate the actual moves - we just need to verify that the constraints allow the transformation.

Learn more about Two Pointers patterns.

Solution Approach

The solution uses a two-pointer technique to validate if the transformation is possible. Here's how the implementation works:

We initialize two pointers i and j to traverse through start and end strings respectively. The algorithm enters an infinite loop that continues until we determine whether the transformation is valid.

Step 1: Skip X characters

while i < n and start[i] == 'X':
    i += 1
while j < n and end[j] == 'X':
    j += 1

We advance both pointers to skip all X characters since they don't affect the validity of the transformation - they merely act as spaces for movement.

Step 2: Check termination conditions

if i >= n and j >= n:
    return True

If both pointers have reached the end of their respective strings, we've successfully matched all non-X characters, so the transformation is valid.

Step 3: Validate character matching

if i >= n or j >= n or start[i] != end[j]:
    return False

If only one pointer has reached the end, or if the current non-X characters don't match, the transformation is impossible. This ensures the sequence of L and R characters remains the same.

Step 4: Check movement constraints

if start[i] == 'L' and i < j:
    return False
if start[i] == 'R' and i > j:
    return False
  • For an L character: if i < j, it means the L needs to move right to reach its target position, which is impossible since L can only move left
  • For an R character: if i > j, it means the R needs to move left to reach its target position, which is impossible since R can only move right

Step 5: Advance pointers

i, j = i + 1, j + 1

After validating the current pair of non-X characters, we move both pointers forward to check the next pair.

The algorithm has a time complexity of O(n) where n is the length of the strings, as we traverse each string at most once. The space complexity is O(1) as we only use a constant amount of extra space for the pointers.

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 a small example to illustrate the solution approach.

Example: start = "RXXLRXRXL" and end = "XRLXRXRLX"

We'll use two pointers i and j starting at position 0 for both strings.

Iteration 1:

  • i = 0: start[0] = 'R' (not X, so we stop)
  • j = 0: end[0] = 'X' β†’ advance to j = 1: end[1] = 'R' (not X, so we stop)
  • Compare: start[0] = 'R' and end[1] = 'R' βœ“ (characters match)
  • Check movement: R at position 0 needs to move to position 1. Since R can move right and 0 < 1, this is valid βœ“
  • Advance: i = 1, j = 2

Iteration 2:

  • i = 1: start[1] = 'X' β†’ advance to i = 2: start[2] = 'X' β†’ advance to i = 3: start[3] = 'L'
  • j = 2: end[2] = 'L' (not X, so we stop)
  • Compare: start[3] = 'L' and end[2] = 'L' βœ“ (characters match)
  • Check movement: L at position 3 needs to move to position 2. Since L can move left and 3 > 2, this is valid βœ“
  • Advance: i = 4, j = 3

Iteration 3:

  • i = 4: start[4] = 'R' (not X, so we stop)
  • j = 3: end[3] = 'X' β†’ advance to j = 4: end[4] = 'R' (not X, so we stop)
  • Compare: start[4] = 'R' and end[4] = 'R' βœ“ (characters match)
  • Check movement: R at position 4 needs to move to position 4. Since positions are equal, no movement needed βœ“
  • Advance: i = 5, j = 5

Iteration 4:

  • i = 5: start[5] = 'X' β†’ advance to i = 6: start[6] = 'R'
  • j = 5: end[5] = 'X' β†’ advance to j = 6: end[6] = 'R'
  • Compare: start[6] = 'R' and end[6] = 'R' βœ“ (characters match)
  • Check movement: R at position 6 needs to move to position 6. No movement needed βœ“
  • Advance: i = 7, j = 7

Iteration 5:

  • i = 7: start[7] = 'X' β†’ advance to i = 8: start[8] = 'L'
  • j = 7: end[7] = 'L' (not X, so we stop)
  • Compare: start[8] = 'L' and end[7] = 'L' βœ“ (characters match)
  • Check movement: L at position 8 needs to move to position 7. Since L can move left and 8 > 7, this is valid βœ“
  • Advance: i = 9, j = 8

Iteration 6:

  • i = 9: We've reached the end of start (i >= n)
  • j = 8: end[8] = 'X' β†’ advance to j = 9: We've reached the end of end (j >= n)
  • Both pointers have reached the end, so return True βœ“

The transformation is possible! Each L moved left (or stayed in place), each R moved right (or stayed in place), and the relative order of all L and R characters was preserved.

Solution Implementation

1class Solution:
2    def canTransform(self, start: str, end: str) -> bool:
3        """
4        Determine if we can transform 'start' string to 'end' string.
5        Rules: 'L' can only move left (bypassing 'X'), 'R' can only move right (bypassing 'X')
6      
7        Args:
8            start: Source string containing 'L', 'R', and 'X' characters
9            end: Target string containing 'L', 'R', and 'X' characters
10          
11        Returns:
12            True if transformation is possible, False otherwise
13        """
14        n = len(start)
15        i = 0  # Pointer for start string
16        j = 0  # Pointer for end string
17      
18        while True:
19            # Skip all 'X' characters in start string
20            while i < n and start[i] == 'X':
21                i += 1
22          
23            # Skip all 'X' characters in end string
24            while j < n and end[j] == 'X':
25                j += 1
26          
27            # Both pointers reached the end - transformation successful
28            if i >= n and j >= n:
29                return True
30          
31            # One pointer reached the end but not the other - transformation impossible
32            # Or the non-X characters don't match
33            if i >= n or j >= n or start[i] != end[j]:
34                return False
35          
36            # 'L' can only move left, so its position in start must be >= its position in end
37            if start[i] == 'L' and i < j:
38                return False
39          
40            # 'R' can only move right, so its position in start must be <= its position in end
41            if start[i] == 'R' and i > j:
42                return False
43          
44            # Move both pointers forward
45            i += 1
46            j += 1
47
1class Solution {
2    public boolean canTransform(String start, String end) {
3        int n = start.length();
4        int startIndex = 0;
5        int endIndex = 0;
6      
7        while (true) {
8            // Skip all 'X' characters in the start string
9            while (startIndex < n && start.charAt(startIndex) == 'X') {
10                startIndex++;
11            }
12          
13            // Skip all 'X' characters in the end string
14            while (endIndex < n && end.charAt(endIndex) == 'X') {
15                endIndex++;
16            }
17          
18            // If both pointers reached the end, transformation is possible
19            if (startIndex == n && endIndex == n) {
20                return true;
21            }
22          
23            // If only one pointer reached the end, or characters don't match
24            if (startIndex == n || endIndex == n || 
25                start.charAt(startIndex) != end.charAt(endIndex)) {
26                return false;
27            }
28          
29            // Check movement constraints:
30            // 'L' can only move left (startIndex should be >= endIndex)
31            // 'R' can only move right (startIndex should be <= endIndex)
32            if ((start.charAt(startIndex) == 'L' && startIndex < endIndex) || 
33                (start.charAt(startIndex) == 'R' && startIndex > endIndex)) {
34                return false;
35            }
36          
37            // Move both pointers forward
38            startIndex++;
39            endIndex++;
40        }
41    }
42}
43
1class Solution {
2public:
3    bool canTransform(string start, string end) {
4        int n = start.size();
5        int startIndex = 0;
6        int endIndex = 0;
7      
8        while (true) {
9            // Skip all 'X' characters in the start string
10            while (startIndex < n && start[startIndex] == 'X') {
11                startIndex++;
12            }
13          
14            // Skip all 'X' characters in the end string
15            while (endIndex < n && end[endIndex] == 'X') {
16                endIndex++;
17            }
18          
19            // If both pointers reached the end, transformation is possible
20            if (startIndex == n && endIndex == n) {
21                return true;
22            }
23          
24            // If only one pointer reached the end, or characters don't match
25            if (startIndex == n || endIndex == n || start[startIndex] != end[endIndex]) {
26                return false;
27            }
28          
29            // 'L' can only move left, so its position in start must be >= its position in end
30            if (start[startIndex] == 'L' && startIndex < endIndex) {
31                return false;
32            }
33          
34            // 'R' can only move right, so its position in start must be <= its position in end
35            if (start[startIndex] == 'R' && startIndex > endIndex) {
36                return false;
37            }
38          
39            // Move to the next non-'X' character
40            startIndex++;
41            endIndex++;
42        }
43    }
44};
45
1function canTransform(start: string, end: string): boolean {
2    const n: number = start.length;
3    let startIndex: number = 0;
4    let endIndex: number = 0;
5  
6    while (true) {
7        // Skip all 'X' characters in the start string
8        while (startIndex < n && start[startIndex] === 'X') {
9            startIndex++;
10        }
11      
12        // Skip all 'X' characters in the end string
13        while (endIndex < n && end[endIndex] === 'X') {
14            endIndex++;
15        }
16      
17        // If both pointers reached the end, transformation is possible
18        if (startIndex === n && endIndex === n) {
19            return true;
20        }
21      
22        // If only one pointer reached the end, or characters don't match
23        if (startIndex === n || endIndex === n || start[startIndex] !== end[endIndex]) {
24            return false;
25        }
26      
27        // 'L' can only move left, so its position in start must be >= its position in end
28        if (start[startIndex] === 'L' && startIndex < endIndex) {
29            return false;
30        }
31      
32        // 'R' can only move right, so its position in start must be <= its position in end
33        if (start[startIndex] === 'R' && startIndex > endIndex) {
34            return false;
35        }
36      
37        // Move to the next non-'X' character
38        startIndex++;
39        endIndex++;
40    }
41}
42

Time and Space Complexity

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

The algorithm uses two pointers i and j that traverse through the start and end strings respectively. In each iteration of the main loop, at least one of the pointers advances. Since both pointers can move at most n positions (the length of the strings), and each pointer movement involves constant-time operations, the total time complexity is linear. Specifically:

  • The inner while loops skip 'X' characters, but each character is visited at most once
  • The comparison and validation operations are all O(1)
  • Both pointers move from index 0 to at most index n

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space:

  • Two integer pointers i and j to track positions in the strings
  • The variable n to store the length
  • No additional data structures are created
  • The space used doesn't scale with the input size

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

Common Pitfalls

1. Forgetting to Check String Lengths

Pitfall: Not verifying that both input strings have the same length before processing. If the strings have different lengths, transformation is impossible, but the algorithm might not handle this gracefully.

Solution: Add a length check at the beginning:

if len(start) != len(end):
    return False

2. Incorrect Boundary Check Order

Pitfall: Checking start[i] or end[j] before verifying that i < n or j < n. This can cause an IndexError when trying to access characters beyond the string length.

Wrong approach:

# Dangerous - might access out of bounds
if start[i] != end[j] or i >= n or j >= n:
    return False

Solution: Always check boundaries before accessing array elements:

# Correct order - check bounds first
if i >= n or j >= n or start[i] != end[j]:
    return False

3. Missing the Case Where Character Counts Don't Match

Pitfall: The current implementation correctly handles this through the two-pointer approach, but a common mistake when implementing this algorithm is to only check positions without verifying that the count of 'L' and 'R' characters matches. For example, "XL" and "RX" both have one non-X character, but they're obviously not transformable.

Solution: The two-pointer approach inherently handles this by ensuring characters match at each step (start[i] != end[j] check), but if implementing differently, explicitly verify:

# Alternative validation approach
if start.count('L') != end.count('L') or start.count('R') != end.count('R'):
    return False

4. Confusing Movement Direction Logic

Pitfall: Mixing up the movement constraints - remembering that 'L' moves left and 'R' moves right is intuitive, but translating this to index comparisons can be confusing. A common mistake is reversing the inequality checks.

Wrong logic:

# Incorrect - reversed logic
if start[i] == 'L' and i > j:  # Wrong! L at position i needs to reach position j
    return False

Solution: Remember the index relationship:

  • If 'L' needs to move from index i to index j, and 'L' can only move left, then i must be β‰₯ j
  • If 'R' needs to move from index i to index j, and 'R' can only move right, then i must be ≀ j

5. Not Handling Edge Cases with All X's

Pitfall: Special cases like strings containing only 'X' characters or empty strings might not be handled correctly if the loop termination conditions aren't properly set.

Solution: The current implementation handles this well with the condition if i >= n and j >= n: return True, which correctly returns True for matching all-X strings or empty strings. However, ensure this check comes before attempting to access string characters.

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

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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

Load More