1138. Alphabet Board Path
Problem Description
You are given an alphabet board arranged as follows:
- Row 0: "abcde"
- Row 1: "fghij"
- Row 2: "klmno"
- Row 3: "pqrst"
- Row 4: "uvwxy"
- Row 5: "z"
Starting at position (0, 0)
which corresponds to the letter 'a', you need to spell out a given target string by moving on the board and selecting letters.
You can perform the following moves:
'U'
- move up one row (if valid)'D'
- move down one row (if valid)'L'
- move left one column (if valid)'R'
- move right one column (if valid)'!'
- select the current letter at your position and add it to your answer
Given a target string, return a sequence of moves that spells out the target. The sequence should use the minimum number of moves, but any valid path that achieves this is acceptable.
For example, to spell "leet":
- Start at 'a' (0,0)
- Move to 'l' at position (2,2): need to go down 2 and right 2, then select with '!'
- Move to 'e' at position (0,4): need to go up 2 and right 2, then select with '!'
- Stay at 'e' and select again with '!'
- Move to 't' at position (3,4): need to go down 3, then select with '!'
The solution handles this by calculating the target position for each character using the formula: row = (ascii_value - 'a') // 5
and column = (ascii_value - 'a') % 5
. It then moves from the current position to the target position following a specific order (left, up, right, down) to avoid invalid moves, especially when dealing with 'z' which is alone on the last row.
Intuition
The key insight is that we need to find the shortest path from our current position to each target character. Since the board is arranged in a grid pattern with letters in alphabetical order, we can easily calculate any letter's position using math: the row is (letter_value // 5)
and column is (letter_value % 5)
, where letter_value = ord(character) - ord('a')
.
For moving between positions, the shortest path is simply the Manhattan distance - we need to move horizontally by |current_col - target_col|
steps and vertically by |current_row - target_row|
steps. The total moves will always be the sum of these two distances regardless of the order we take them.
However, there's a crucial edge case: the letter 'z' sits alone on row 5. This creates a problem because positions (5,1), (5,2), (5,3), and (5,4) don't exist on the board. If we're not careful about our movement order, we might try to move through these invalid positions.
The solution cleverly addresses this by enforcing a specific movement order: Left → Up → Right → Down. This order ensures we never move through invalid positions. Here's why:
-
When moving to 'z' at position (5,0): By moving left first and up second, we ensure we reach column 0 before descending to row 5. Then moving right (nothing to do since 'z' is at column 0) and down gets us safely to 'z'.
-
When moving from 'z' to any other letter: By moving left first (nothing to do since we're already at column 0) and up second, we immediately leave row 5 before attempting any rightward movement, avoiding the invalid positions.
This movement order works for all other transitions as well since the rest of the board is fully populated, making any path valid. The specific order just guarantees we handle the 'z' edge case correctly while still achieving the minimum number of moves.
Solution Approach
The implementation follows a straightforward simulation approach where we track our current position and move to each target character one by one.
Algorithm Steps:
-
Initialize position: Start at
(i=0, j=0)
representing position 'a' on the board. -
Process each character: For each character
c
in the target string:- Calculate the target position using mathematical mapping:
v = ord(c) - ord("a")
gives us the character's position in the alphabet (0-25)x = v // 5
gives us the target rowy = v % 5
gives us the target column
- Calculate the target position using mathematical mapping:
-
Move to target position: Execute movements in the specific order (Left → Up → Right → Down):
- Move Left: While
j > y
, decrementj
and append "L" to the result - Move Up: While
i > x
, decrementi
and append "U" to the result - Move Right: While
j < y
, incrementj
and append "R" to the result - Move Down: While
i < x
, incrementi
and append "D" to the result
- Move Left: While
-
Select character: After reaching the target position, append "!" to select the current character.
-
Build result: Join all the movement commands into a single string.
Why this order works:
The movement order is critical for handling the edge case of 'z'. Consider two scenarios:
-
Moving to 'z' from any letter: We first move left to column 0, then up if needed, ensuring we're at column 0 before moving down to row 5. This avoids invalid positions like (5,2).
-
Moving from 'z' to any letter: We're at (5,0). Moving left does nothing, then we move up to leave row 5, then right to the target column. This ensures we exit row 5 before attempting any rightward movement.
Time Complexity: O(n * m)
where n
is the length of the target string and m
is the maximum Manhattan distance between any two characters (at most 9 moves between 'a' and 'z').
Space Complexity: O(n * m)
for storing the result string with all movement commands.
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 trace through spelling the word "za" to demonstrate the solution approach, particularly highlighting the edge case handling.
Initial Setup:
- Start at position (0, 0) which is letter 'a'
- Target string: "za"
Step 1: Move to 'z'
- Current position: (0, 0)
- Calculate target position for 'z':
v = ord('z') - ord('a') = 25
x = 25 // 5 = 5
(target row)y = 25 % 5 = 0
(target column)
- Target position: (5, 0)
Now apply movements in order (Left → Up → Right → Down):
- Left: Current j=0, target y=0. No movement needed.
- Up: Current i=0, target x=5. No movement needed (we're going down, not up).
- Right: Current j=0, target y=0. No movement needed.
- Down: Current i=0, target x=5. Move down 5 times: "DDDDD"
- Select: Add "!"
Result so far: "DDDDD!"
Step 2: Move to 'a'
- Current position: (5, 0) - we're at 'z'
- Calculate target position for 'a':
v = ord('a') - ord('a') = 0
x = 0 // 5 = 0
(target row)y = 0 % 5 = 0
(target column)
- Target position: (0, 0)
Apply movements in order (Left → Up → Right → Down):
- Left: Current j=0, target y=0. No movement needed.
- Up: Current i=5, target x=0. Move up 5 times: "UUUUU"
- Right: Current j=0, target y=0. No movement needed.
- Down: Current i=0, target x=0. No movement needed.
- Select: Add "!"
Final result: "DDDDD!UUUUU!"
Why the order matters: Notice when moving from 'z' at (5,0) to 'a', we move UP first before any potential RIGHT movements. If we had tried to move right first from position (5,0), we would have attempted to reach invalid positions like (5,1) or (5,2) which don't exist on the board. By moving up first, we safely leave row 5 before any horizontal movements, ensuring we only traverse valid positions.
Solution Implementation
1class Solution:
2 def alphabetBoardPath(self, target: str) -> str:
3 """
4 Find the path on an alphabet board to spell out the target string.
5 Board layout:
6 a b c d e
7 f g h i j
8 k l m n o
9 p q r s t
10 u v w x y
11 z
12 """
13 # Initialize starting position at 'a' (0, 0)
14 current_row = 0
15 current_col = 0
16 result_path = []
17
18 for char in target:
19 # Calculate target position on the board
20 # Each row has 5 characters, so divmod by 5 gives row and column
21 char_value = ord(char) - ord('a')
22 target_row = char_value // 5
23 target_col = char_value % 5
24
25 # Move left first (important for handling 'z' at position (5, 0))
26 # Moving left before down prevents going out of bounds
27 while current_col > target_col:
28 current_col -= 1
29 result_path.append('L')
30
31 # Move up
32 while current_row > target_row:
33 current_row -= 1
34 result_path.append('U')
35
36 # Move right
37 while current_col < target_col:
38 current_col += 1
39 result_path.append('R')
40
41 # Move down last (important for handling 'z' at position (5, 0))
42 # Moving down after right ensures we don't go out of bounds
43 while current_row < target_row:
44 current_row += 1
45 result_path.append('D')
46
47 # Add selection marker for current character
48 result_path.append('!')
49
50 # Join all moves into a single string
51 return ''.join(result_path)
52
1class Solution {
2 public String alphabetBoardPath(String target) {
3 // StringBuilder to construct the result path
4 StringBuilder path = new StringBuilder();
5
6 // Current position on the board (starting at 'a' which is at position [0, 0])
7 int currentRow = 0;
8 int currentCol = 0;
9
10 // Process each character in the target string
11 for (int index = 0; index < target.length(); index++) {
12 // Calculate the target position for the current character
13 // Convert character to its position number (0-25)
14 int charValue = target.charAt(index) - 'a';
15
16 // Calculate row and column of the target character on the board
17 // Board has 5 columns, so row = charValue / 5, column = charValue % 5
18 int targetRow = charValue / 5;
19 int targetCol = charValue % 5;
20
21 // Move in a specific order to avoid getting stuck at 'z' (position [5, 0])
22 // The order is important: Left -> Up -> Right -> Down
23 // This ensures we can always reach any character, including 'z'
24
25 // Move left if needed (decrease column)
26 while (currentCol > targetCol) {
27 currentCol--;
28 path.append('L');
29 }
30
31 // Move up if needed (decrease row)
32 while (currentRow > targetRow) {
33 currentRow--;
34 path.append('U');
35 }
36
37 // Move right if needed (increase column)
38 while (currentCol < targetCol) {
39 currentCol++;
40 path.append('R');
41 }
42
43 // Move down if needed (increase row)
44 while (currentRow < targetRow) {
45 currentRow++;
46 path.append('D');
47 }
48
49 // Add the character at current position to the result
50 path.append('!');
51 }
52
53 // Return the complete path as a string
54 return path.toString();
55 }
56}
57
1class Solution {
2public:
3 string alphabetBoardPath(string target) {
4 string result;
5 int currentRow = 0, currentCol = 0; // Starting position at 'a' (0,0)
6
7 for (char& targetChar : target) {
8 // Calculate target position on the board
9 int charValue = targetChar - 'a'; // Convert character to 0-based index
10 int targetRow = charValue / 5; // Row number (5 characters per row)
11 int targetCol = charValue % 5; // Column number within the row
12
13 // Move left first (important for 'z' at position (5,0))
14 while (currentCol > targetCol) {
15 --currentCol;
16 result += 'L';
17 }
18
19 // Move up (must come before down when dealing with 'z')
20 while (currentRow > targetRow) {
21 --currentRow;
22 result += 'U';
23 }
24
25 // Move right
26 while (currentCol < targetCol) {
27 ++currentCol;
28 result += 'R';
29 }
30
31 // Move down last (important for reaching 'z' at position (5,0))
32 while (currentRow < targetRow) {
33 ++currentRow;
34 result += 'D';
35 }
36
37 // Add character selection marker
38 result += '!';
39 }
40
41 return result;
42 }
43};
44
1function alphabetBoardPath(target: string): string {
2 let result: string = "";
3 let currentRow: number = 0; // Current row position, starting at 'a' (0,0)
4 let currentCol: number = 0; // Current column position, starting at 'a' (0,0)
5
6 // Process each character in the target string
7 for (const targetChar of target) {
8 // Calculate the target position on the alphabet board
9 const charValue: number = targetChar.charCodeAt(0) - 'a'.charCodeAt(0); // Convert character to 0-based index
10 const targetRow: number = Math.floor(charValue / 5); // Calculate row number (5 characters per row)
11 const targetCol: number = charValue % 5; // Calculate column number within the row
12
13 // Move left first (critical for handling 'z' at position (5,0))
14 // Moving left before down prevents going out of bounds when leaving 'z'
15 while (currentCol > targetCol) {
16 currentCol--;
17 result += 'L';
18 }
19
20 // Move up second (must occur before moving down to handle 'z' edge case)
21 // This ensures we can move from 'z' to other characters without going out of bounds
22 while (currentRow > targetRow) {
23 currentRow--;
24 result += 'U';
25 }
26
27 // Move right third
28 // Safe to move right after vertical adjustments
29 while (currentCol < targetCol) {
30 currentCol++;
31 result += 'R';
32 }
33
34 // Move down last (essential for reaching 'z' at position (5,0))
35 // Moving down last ensures we don't go out of bounds when targeting 'z'
36 while (currentRow < targetRow) {
37 currentRow++;
38 result += 'D';
39 }
40
41 // Mark the current character as selected
42 result += '!';
43 }
44
45 return result;
46}
47
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the target string. The algorithm iterates through each character in the target string exactly once. For each character, it performs a constant amount of work - calculating the target position (x, y)
takes O(1)
time, and moving from the current position to the target position takes at most O(1)
time since the board has fixed dimensions (6×5 grid). The maximum distance between any two positions on the board is bounded by a constant (at most 5 moves vertically and 4 moves horizontally), so the inner while loops execute a constant number of times for each character.
Space Complexity: O(1)
if we exclude the space used for the output string. The algorithm uses only a fixed number of variables (i
, j
, v
, x
, y
, c
) regardless of the input size. The ans
list grows proportionally to store the result, but as mentioned in the reference, we ignore the space consumption of the answer itself when analyzing space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Invalid Board Access When Moving to/from 'z'
The most critical pitfall in this problem is not handling the special case of 'z' correctly. The letter 'z' is located at position (5, 0), making it the only character in row 5. If you attempt to move horizontally while on row 5 to any column other than 0, or if you try to reach row 5 at any column other than 0, you'll access an invalid position on the board.
Problematic Approach:
# WRONG: Moving in arbitrary order without considering 'z'
def alphabetBoardPath(self, target: str) -> str:
i, j = 0, 0
result = []
for c in target:
x = (ord(c) - ord('a')) // 5
y = (ord(c) - ord('a')) % 5
# This can fail when moving to/from 'z'
while i < x:
i += 1
result.append('D')
while i > x:
i -= 1
result.append('U')
while j < y:
j += 1
result.append('R')
while j > y:
j -= 1
result.append('L')
result.append('!')
return ''.join(result)
Example of Failure:
- Moving from 'y' (4, 4) to 'z' (5, 0):
- If we move down first: (4,4) → (5,4) - INVALID! Row 5 only has column 0
- We must move left to column 0 first, then down
Solution: The correct approach uses a specific movement order: Left → Up → Right → Down
This order ensures:
- When moving TO 'z': We move left to column 0 first, then down to row 5
- When moving FROM 'z': We move up first to leave row 5, then right to the target column
Correct Implementation Pattern:
# Move in this specific order to handle 'z' safely while current_col > target_col: # Left first current_col -= 1 result_path.append('L') while current_row > target_row: # Up second current_row -= 1 result_path.append('U') while current_col < target_col: # Right third current_col += 1 result_path.append('R') while current_row < target_row: # Down last current_row += 1 result_path.append('D')
This movement order guarantees we never attempt to access invalid positions on the board, making it a robust solution for all possible character transitions.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!