2337. Move Pieces to Obtain a String
Problem Description
The given problem is about transforming one string into another using specific movement rules for the characters within the strings. The strings start
and target
are of the same length n
and consist only of characters 'L', 'R', and ''. An 'L' can move only left if there is a blank space ('') directly to its left, and an 'R' can move only right if there is a blank space directly to its right. The goal is to determine if the start
string can be transformed into the target
string by applying these movements any number of times. The output should be true
if the transformation is possible, otherwise false
.
Intuition
The intuition behind the solution involves two key insights:
-
The relative order of the pieces 'L' and 'R' cannot change because 'L' can only move left and 'R' can only move right. Therefore, if in the
target
string, an 'R' appears to the left of an 'L' which was to its right in thestart
string, the transformation is not possible. -
Given the same relative order, an 'L' can only move to the left, so its position in the
target
string must not be to the right of its position in thestart
string. Similarly, an 'R' can only move to the right, so its position in thetarget
string must not be to the left of its position in thestart
string.
With these insights in mind, the solution approach is straightforward:
-
Ignore the blank spaces and compare the positions of the non-blank characters in both strings. If there are a different number of 'L' and 'R' characters or they are in a different relative order, the target cannot be reached.
-
If the characters are in the same order, check if 'L' in
target
is never to the right of its position instart
, and 'R' is never to the left. If this is true for all characters, then the transformation is possible and returntrue
. Otherwise, returnfalse
.
Learn more about Two Pointers patterns.
Solution Approach
The Python code provided earlier implements the solution approach effectively. It uses the following steps and Python-specific data structures and functions:
-
Filtering and Pairing: The comprehension lists
a
andb
filter out the blank spaces '_' and create lists of tuples that contain the character and its index (position) from thestart
andtarget
strings, respectively. This is done using theenumerate
function which gives the index along with each character as you iterate over the string:a = [(v, i) for i, v in enumerate(start) if v != '_'] b = [(v, i) for i, v in enumerate(target) if v != '_']
-
Checking the Length: This step checks if the lengths of the filtered lists are equal. If they are not, it is not possible to get
target
fromstart
since the number of movable pieces is different, so the function returnsFalse
.if len(a) != len(b): return False
-
Comparing Corresponding Pairs: The use of the
zip
function takes pairs froma
andb
in a parallel manner to ensure we're comparing pieces from thestart
andtarget
strings that are supposed to correspond to each other. -
Piece Type and Position Validation: For each pair of tuples
(c, i)
froma
, and(d, j)
fromb
, the following checks are performed:- If the piece type is not the same (
c != d
), it cannot be a valid transformation since 'L' cannot become 'R' and vice versa, henceFalse
is returned. - If the piece is 'L', it should not be to the right in
target
as compared tostart
(i < j
case), since 'L' can only move left. - If the piece is 'R', it should not be to the left in
target
as compared tostart
(i > j
case), since 'R' can only move right.
If any of these conditions are violated, the function returns
False
.for (c, i), (d, j) in zip(a, b): if c != d or (c == 'L' and i < j) or (c == 'R' and i > j): return False
- If the piece type is not the same (
-
Returning True: If none of the above checks fail, it means the transformation from
start
totarget
is possible, henceTrue
is returned at the end of the function.
By using tuples for storing character and index pairs, and the zip
function to iterate over them, we avoid the need for more complex data structures. This simplifies the algorithm and improves its readability and performance.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider two strings as an example:
start
:"R__L"
target
:"__RL"
Following the steps of the solution approach:
-
Filtering and Pairing:
For
start
:a = [(v, i) for i, v in enumerate("R__L") if v != '_'] = [('R', 0), ('L', 3)]
For
target
:b = [(v, i) for i, v in enumerate("__RL") if v != '_'] = [('R', 2), ('L', 3)]
-
Checking the Length:
Length of
a
: 2 Length ofb
: 2Since both lengths are equal, we proceed.
-
Comparing Corresponding Pairs:
We utilize
zip
to compare each pair froma
andb
.Pairs to compare:
('R', 0) from `start` and ('R', 2) from `target` ('L', 3) from `start` and ('L', 3) from `target`
-
Piece Type and Position Validation:
For the first pair:
- Piece type is the same, both are 'R'.
- 'R' is in position 0 in
start
and position 2 intarget
, which is a valid move for 'R', since it can move to the right.
For the second pair:
- Piece type is the same, both are 'L'.
- 'L' is in position 3 in
start
and position 3 intarget
, indicating no movement, which is also valid since 'L' cannot move to the right.
Since both pairs are valid according to the movement rules, we can continue.
-
Returning True:
Because none of the validation checks failed, the function would return
True
, meaning it is possible to transform thestart
string"R__L"
into thetarget
string"__RL"
by moving the 'R' two spaces to the right.
Solution Implementation
1class Solution:
2 def canChange(self, start: str, target: str) -> bool:
3 # Create pairs of (character, index) for non '_' characters in start string.
4 start_positions = [(char, index) for index, char in enumerate(start) if char != '_']
5
6 # Create pairs of (character, index) for non '_' characters in target string.
7 target_positions = [(char, index) for index, char in enumerate(target) if char != '_']
8
9 # If the number of non '_' characters in start and target are different, return False.
10 if len(start_positions) != len(target_positions):
11 return False
12
13 # Iterate over the pairs of start and target together.
14 for (start_char, start_index), (target_char, target_index) in zip(start_positions, target_positions):
15 # If the characters are not the same, the transformation is not possible.
16 if start_char != target_char:
17 return False
18 # A 'L' character in start should have an index greater than or equal to that in target.
19 if start_char == 'L' and start_index < target_index:
20 return False
21 # A 'R' character in start should have an index less than or equal to that in target.
22 if start_char == 'R' and start_index > target_index:
23 return False
24
25 # If all conditions are met, return True indicating the transformation is possible.
26 return True
27
1class Solution {
2 // Main method to check if it's possible to transform the start string to the target string
3 public boolean canChange(String start, String target) {
4 // Parse the strings to obtain the positions and types of 'L' and 'R' characters
5 List<int[]> startPosList = parseString(start);
6 List<int[]> targetPosList = parseString(target);
7
8 // If the number of 'L' and 'R' characters in both strings is different, transformation is not possible
9 if (startPosList.size() != targetPosList.size()) {
10 return false;
11 }
12
13 // Compare the positions and types of 'L' and 'R' characters in the two lists
14 for (int i = 0; i < startPosList.size(); ++i) {
15 int[] startPos = startPosList.get(i);
16 int[] targetPos = targetPosList.get(i);
17
18 // If the types of characters (L or R) are different at any point, transformation is not possible
19 if (startPos[0] != targetPos[0]) {
20 return false;
21 }
22 // If 'L' in start is to the right of 'L' in target, transformation is not possible as 'L' only moves left
23 if (startPos[0] == 1 && startPos[1] < targetPos[1]) {
24 return false;
25 }
26 // If 'R' in start is to the left of 'R' in target, transformation is not possible as 'R' only moves right
27 if (startPos[0] == 2 && startPos[1] > targetPos[1]) {
28 return false;
29 }
30 }
31 // All checks passed, transformation is possible
32 return true;
33 }
34
35 // Helper method to parse a string to a list of positions and types for 'L' and 'R'
36 private List<int[]> parseString(String s) {
37 List<int[]> result = new ArrayList<>();
38 for (int i = 0; i < s.length(); ++i) {
39 char currentChar = s.charAt(i);
40 // If the current character is 'L', add to the list with type 1
41 if (currentChar == 'L') {
42 result.add(new int[] {1, i});
43 }
44 // If the current character is 'R', add to the list with type 2
45 else if (currentChar == 'R') {
46 result.add(new int[] {2, i});
47 }
48 }
49 return result;
50 }
51}
52
1#include <vector>
2#include <string>
3#include <utility>
4
5using namespace std;
6
7// Define 'pii' as an alias for 'pair<int, int>'.
8using pii = pair<int, int>;
9
10class Solution {
11public:
12 // Main function to determine if one string can transition to another.
13 bool canChange(string start, string target) {
14 // Extract the positions and directions of 'L' and 'R' from both strings.
15 auto startPositions = extractPositions(start);
16 auto targetPositions = extractPositions(target);
17
18 // If the number of 'L' and 'R' characters are different, return false.
19 if (startPositions.size() != targetPositions.size()) return false;
20
21 // Check each corresponding 'L' and 'R' character from start and target.
22 for (int i = 0; i < startPositions.size(); ++i) {
23 auto startPosition = startPositions[i], targetPosition = targetPositions[i];
24 // If the direction is different, the change is not possible.
25 if (startPosition.first != targetPosition.first) return false;
26 // If an 'L' in start is to the right of the 'L' in target, change is not possible.
27 if (startPosition.first == 1 && startPosition.second < targetPosition.second) return false;
28 // If an 'R' in start is to the left of the 'R' in target, change is not possible.
29 if (startPosition.first == 2 && startPosition.second > targetPosition.second) return false;
30 }
31
32 // If all 'L' and 'R' can be moved to their target positions, return true.
33 return true;
34 }
35
36 // Helper function to extract the positions and directions of 'L' and 'R'.
37 vector<pii> extractPositions(string s) {
38 vector<pii> positions;
39 for (int i = 0; i < s.size(); ++i) {
40 // If the character is 'L', associate it with direction 1.
41 if (s[i] == 'L')
42 positions.push_back({1, i});
43 // If the character is 'R', associate it with direction 2.
44 else if (s[i] == 'R')
45 positions.push_back({2, i});
46 }
47 return positions;
48 }
49};
50
1function canChange(start: string, target: string): boolean {
2 const length = start.length; // The length of the start and target strings
3 let startIdx = 0; // Start index for iterating through the start string
4 let targetIdx = 0; // Start index for iterating through the target string
5
6 while (true) {
7 // Skip all the underscores in the start string
8 while (startIdx < length && start[startIdx] === '_') {
9 ++startIdx;
10 }
11 // Skip all the underscores in the target string
12 while (targetIdx < length && target[targetIdx] === '_') {
13 ++targetIdx;
14 }
15
16 // If both indices have reached the end, the strings can be changed to each other
17 if (startIdx === length && targetIdx === length) {
18 return true;
19 }
20
21 // If one index reaches the end before the other, or the characters at the current indices do not match,
22 // the strings cannot be changed to each other
23 if (startIdx === length || targetIdx === length || start[startIdx] !== target[targetIdx]) {
24 return false;
25 }
26
27 // If the character is 'L' and the start index is ahead of the target index, or
28 // if the character is 'R' and the start index is behind the target index,
29 // it's not possible to change the strings according to the rules
30 if ((start[startIdx] === 'L' && startIdx < targetIdx) || (start[startIdx] === 'R' && startIdx > targetIdx)) {
31 return false;
32 }
33
34 // Move both indices forward
35 ++startIdx;
36 ++targetIdx;
37 }
38}
39
Time and Space Complexity
Time Complexity
The given Python function canChange
checks whether it is possible to transform the start
string into the target
string under certain conditions. Analyzing the time complexity involves a few steps:
-
Creation of list
a
: The list comprehension iterates over each character in thestart
string and includes only non-underscore characters. Therefore, this operation has a time complexity ofO(n)
wheren
is the length of thestart
string. -
Creation of list
b
: Similarly, the creation of listb
iterates over each character in thetarget
string and has a time complexity ofO(n)
. -
Comparison of lengths: Checking if
len(a)
is equal tolen(b)
takes constant time,O(1)
. -
Zipping and iterating: Zipping the two lists
a
andb
and iterating over them to compare elements has a time complexity ofO(m)
, wherem
is the number of non-underscore characters in the strings, which is at mostn
.
Given these steps occur sequentially, the overall time complexity is dominated by the terms with O(n)
, leading to a total time complexity of O(n)
.
Space Complexity
-
List
a
andb
store the non-underscore characters and their respective indices fromstart
andtarget
. These take space proportional to the number of non-underscore characters, which isO(m)
wherem
is the number of such characters. -
The space taken by the zip object and iteration is negligible since they don't store values but only reference the elements in
a
andb
.
Hence, the overall space complexity of the function canChange
is O(m)
, where m
is the number of non-underscore characters and m <= n
. If we consider that all characters could be non-underscore, the space complexity would also be O(n)
in the worst case.
Learn more about how to find time and space complexity quickly using problem constraints.
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!