1958. Check if Move is Legal
Problem Description
You are playing a game on an 8x8 board similar to Reversi/Othello. The board contains three types of cells:
.
represents a free/empty cellW
represents a white pieceB
represents a black piece
You want to place a piece of your color (color
) at position (rMove, cMove)
. However, this move is only legal if it creates a "good line."
A good line is defined as:
- A line (horizontal, vertical, or diagonal) of 3 or more cells
- The two endpoints must be the same color
- All cells between the endpoints must be the opposite color
- No free cells (
.
) can be in the middle of the line
When you place your piece at (rMove, cMove)
, it becomes one endpoint of the good line. The other endpoint must already exist on the board in the same color as your piece.
For example, if you're playing black (B
) and want to place at position X:
- Valid:
B W W X
→ becomesB W W B
(a good line of length 4) - Valid:
B W X
→ becomesB W B
(a good line of length 3) - Invalid:
B . W X
→ has a free cell in the middle - Invalid:
B X
→ only 2 cells, need at least 3
Given the board state, the position where you want to move (rMove, cMove)
, and your color, determine if placing a piece at that position would be a legal move by checking if it would form at least one good line in any of the 8 directions (horizontal, vertical, or diagonal).
Return true
if the move is legal, false
otherwise.
Intuition
To check if a move is legal, we need to verify if placing our piece at (rMove, cMove)
would create at least one good line in any direction.
From the position where we want to place our piece, we can extend in 8 possible directions: up, down, left, right, and the four diagonals. Each direction can be represented by a pair of offsets (a, b)
where a
and b
can be -1, 0, or 1 (excluding the case where both are 0, which means no movement).
For each direction, we start from (rMove, cMove)
and move step by step in that direction. As we move:
- We count how many cells we've traversed (
cnt
) - We check what type of cell we encounter at each step
The key insight is that for a valid good line:
- We must first pass through one or more cells of the opposite color
- Then we must reach a cell of our own color
- The total length must be at least 3 (including our starting position)
So when traversing in a direction, if we encounter:
- A cell of our own color AND we've already moved at least 2 steps (
cnt > 1
), we've found a valid good line - An empty cell (
.
) or our own color too early (whencnt <= 1
), this direction won't form a good line, so we stop checking this direction - A cell of the opposite color, we continue moving in that direction
We only need to find one valid good line in any of the 8 directions to confirm the move is legal. If we check all directions and don't find any good line, the move is illegal.
Solution Approach
The solution uses a brute-force enumeration approach to check all 8 possible directions from the target position.
We iterate through all possible direction vectors using two nested loops where a
and b
range from -1 to 1. This gives us 9 combinations, but we skip the case where both a = 0
and b = 0
(no movement), leaving us with 8 directions:
- Horizontal:
(-1, 0)
and(1, 0)
- Vertical:
(0, -1)
and(0, 1)
- Diagonal:
(-1, -1)
,(-1, 1)
,(1, -1)
, and(1, 1)
For each direction (a, b)
:
-
Initialize position and counter: Start from
(rMove, cMove)
and setcnt = 0
to track the number of cells traversed. -
Traverse in the current direction: Use a while loop to move step by step:
- Check boundaries:
0 <= i + a < 8 and 0 <= j + b < 8
- Increment counter:
cnt += 1
- Update position:
i, j = i + a, j + b
- Check boundaries:
-
Check for valid good line: At each step, examine the current cell
board[i][j]
:- If
cnt > 1
andboard[i][j] == color
: We've found a cell of our color after passing through at least one other cell. This forms a valid good line, so returnTrue
. - If
board[i][j]
is either our color (whencnt <= 1
) or an empty cell.
: This direction cannot form a good line, sobreak
and try the next direction. - Otherwise (the cell is the opposite color): Continue traversing in this direction.
- If
-
Return result: If no valid good line is found after checking all 8 directions, return
False
.
The time complexity is O(1)
since the board size is fixed at 8×8, and we check at most 8 directions with at most 7 steps each. The space complexity is also O(1)
as we only use a constant amount of extra variables.
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 walk through a concrete example to illustrate the solution approach.
Given:
- Board (5x5 for simplicity):
0 1 2 3 4 0 . . . . . 1 . B W W . 2 . . . . . 3 . . . . . 4 . . . . .
- We want to place a Black piece (
B
) at position(1, 4)
- So
rMove = 1
,cMove = 4
,color = 'B'
Step-by-step execution:
We'll check all 8 directions from position (1, 4)
:
-
Direction (-1, -1) - diagonal up-left:
- Start at (1, 4), cnt = 0
- Move to (0, 3): cnt = 1, board[0][3] = '.' (empty)
- Break immediately (empty cell found)
- Result: No good line
-
Direction (-1, 0) - up:
- Start at (1, 4), cnt = 0
- Move to (0, 4): cnt = 1, board[0][4] = '.' (empty)
- Break immediately
- Result: No good line
-
Direction (-1, 1) - diagonal up-right:
- Start at (1, 4), cnt = 0
- Would move to (0, 5): out of bounds
- Result: No good line
-
Direction (0, -1) - left:
- Start at (1, 4), cnt = 0
- Move to (1, 3): cnt = 1, board[1][3] = 'W' (opposite color)
- Continue...
- Move to (1, 2): cnt = 2, board[1][2] = 'W' (opposite color)
- Continue...
- Move to (1, 1): cnt = 3, board[1][1] = 'B' (our color!)
- Since cnt > 1 and we found our color, we have a good line!
- The line would be:
B W W B
(length 4) - Return True
We found a valid good line in the left direction, so the move is legal. The function returns True
without needing to check the remaining directions.
If we had continued checking:
- Directions (0, 1), (1, -1), (1, 0), (1, 1) would all encounter empty cells or go out of bounds quickly
The key insight: We only needed to find ONE valid good line to confirm the move is legal. The left direction gave us the line B W W B
after placing our piece, which satisfies all requirements (length ≥ 3, same color endpoints, opposite color in between, no empty cells).
Solution Implementation
1class Solution:
2 def checkMove(
3 self, board: List[List[str]], rMove: int, cMove: int, color: str
4 ) -> bool:
5 """
6 Check if placing a piece at (rMove, cMove) creates a valid line.
7 A valid line must have at least one opponent piece between the placed piece
8 and another piece of the same color.
9
10 Args:
11 board: 8x8 game board
12 rMove: Row position of the move
13 cMove: Column position of the move
14 color: Color of the piece being placed ('B' or 'W')
15
16 Returns:
17 True if the move creates at least one valid line, False otherwise
18 """
19 # Check all 8 directions from the placement position
20 for row_direction in range(-1, 2):
21 for col_direction in range(-1, 2):
22 # Skip the case where both directions are 0 (no movement)
23 if row_direction == 0 and col_direction == 0:
24 continue
25
26 # Start from the placement position
27 current_row, current_col = rMove, cMove
28 pieces_in_line = 0
29
30 # Move in the current direction while staying within board bounds
31 while (0 <= current_row + row_direction < 8 and
32 0 <= current_col + col_direction < 8):
33 pieces_in_line += 1
34 current_row += row_direction
35 current_col += col_direction
36
37 # If we find our color after at least one opponent piece, it's valid
38 if pieces_in_line > 1 and board[current_row][current_col] == color:
39 return True
40
41 # Stop if we hit our own color too early or an empty cell
42 if board[current_row][current_col] in (color, "."):
43 break
44
45 return False
46
1class Solution {
2 /**
3 * Checks if placing a piece at the given position creates a valid move.
4 * A valid move requires forming a "good line" - a line of at least 3 cells where:
5 * - The endpoints have the same color
6 * - All cells between endpoints have the opposite color
7 *
8 * @param board The 8x8 game board
9 * @param rMove Row position of the move
10 * @param cMove Column position of the move
11 * @param color Color of the piece being placed ('B' or 'W')
12 * @return true if the move creates at least one good line, false otherwise
13 */
14 public boolean checkMove(char[][] board, int rMove, int cMove, char color) {
15 // Check all 8 directions from the placed piece
16 for (int rowDirection = -1; rowDirection <= 1; ++rowDirection) {
17 for (int colDirection = -1; colDirection <= 1; ++colDirection) {
18 // Skip the case where both directions are 0 (no movement)
19 if (rowDirection == 0 && colDirection == 0) {
20 continue;
21 }
22
23 // Initialize current position to the starting move position
24 int currentRow = rMove;
25 int currentCol = cMove;
26 int cellsChecked = 0;
27
28 // Continue checking in the current direction while within board bounds
29 while (currentRow + rowDirection >= 0 && currentRow + rowDirection < 8 &&
30 currentCol + colDirection >= 0 && currentCol + colDirection < 8) {
31 // Move one step in the current direction
32 currentRow += rowDirection;
33 currentCol += colDirection;
34 cellsChecked++;
35
36 // Check if we found a matching color after at least one opposite color
37 // This forms a valid line (start color -> opposite colors -> same color)
38 if (cellsChecked > 1 && board[currentRow][currentCol] == color) {
39 return true;
40 }
41
42 // Stop checking this direction if we hit our own color immediately
43 // or an empty cell (can't form a valid line)
44 if (board[currentRow][currentCol] == color || board[currentRow][currentCol] == '.') {
45 break;
46 }
47 }
48 }
49 }
50
51 // No valid line found in any direction
52 return false;
53 }
54}
55
1class Solution {
2public:
3 bool checkMove(vector<vector<char>>& board, int rMove, int cMove, char color) {
4 // Try all 8 directions around the placed piece
5 for (int directionRow = -1; directionRow <= 1; ++directionRow) {
6 for (int directionCol = -1; directionCol <= 1; ++directionCol) {
7 // Skip the center position (0, 0) which represents no movement
8 if (directionRow == 0 && directionCol == 0) {
9 continue;
10 }
11
12 // Start from the move position
13 int currentRow = rMove;
14 int currentCol = cMove;
15 int stepsCount = 0;
16
17 // Continue moving in the current direction while within board boundaries
18 while (0 <= currentRow + directionRow && currentRow + directionRow < 8 &&
19 0 <= currentCol + directionCol && currentCol + directionCol < 8) {
20 // Move one step in the current direction
21 currentRow += directionRow;
22 currentCol += directionCol;
23 stepsCount++;
24
25 // Check if we found a valid endpoint (same color) after at least one opposite color
26 if (stepsCount > 1 && board[currentRow][currentCol] == color) {
27 return true; // Found a valid line with pieces to flip
28 }
29
30 // Stop if we encounter our own color too early or an empty cell
31 if (board[currentRow][currentCol] == color || board[currentRow][currentCol] == '.') {
32 break;
33 }
34 // Continue if we encounter the opposite color
35 }
36 }
37 }
38
39 // No valid move found in any direction
40 return false;
41 }
42};
43
1/**
2 * Checks if a move is valid in a game (likely Othello/Reversi)
3 * by verifying if placing a piece would flip at least one opponent's piece
4 *
5 * @param board - 8x8 game board with 'B' (black), 'W' (white), or '.' (empty)
6 * @param rMove - Row index of the proposed move (0-7)
7 * @param cMove - Column index of the proposed move (0-7)
8 * @param color - Color of the piece to be placed ('B' or 'W')
9 * @returns true if the move is valid, false otherwise
10 */
11function checkMove(board: string[][], rMove: number, cMove: number, color: string): boolean {
12 // Check all 8 directions around the placement position
13 for (let deltaRow = -1; deltaRow <= 1; deltaRow++) {
14 for (let deltaCol = -1; deltaCol <= 1; deltaCol++) {
15 // Skip the center position (no direction)
16 if (deltaRow === 0 && deltaCol === 0) {
17 continue;
18 }
19
20 // Initialize current position to the move position
21 let currentRow: number = rMove;
22 let currentCol: number = cMove;
23 let stepsCount: number = 0;
24
25 // Traverse in the current direction while within board boundaries
26 while (currentRow + deltaRow >= 0 && currentRow + deltaRow < 8 &&
27 currentCol + deltaCol >= 0 && currentCol + deltaCol < 8) {
28 // Move one step in the current direction
29 currentRow += deltaRow;
30 currentCol += deltaCol;
31 stepsCount++;
32
33 // Check if we found our color after at least one opponent piece
34 if (stepsCount > 1 && board[currentRow][currentCol] === color) {
35 return true; // Valid move found
36 }
37
38 // Stop if we hit our own color immediately or an empty cell
39 if (board[currentRow][currentCol] === color || board[currentRow][currentCol] === '.') {
40 break;
41 }
42 }
43 }
44 }
45
46 // No valid direction found
47 return false;
48}
49
Time and Space Complexity
Time Complexity: O(m + n)
where m
is the number of rows and n
is the number of columns in the board. Since the board is fixed at 8×8, this simplifies to O(8 + 8) = O(16) = O(1)
.
The analysis works as follows:
- The outer loops iterate through 8 directions (3×3 grid minus the center), giving us a constant factor of 8
- For each direction
(a, b)
, the while loop traverses at mostmax(m, n)
cells in that direction before hitting a boundary - In the worst case, we might traverse the entire diagonal, row, or column, which is at most
max(m, n) = 8
cells - Therefore, the total operations are
8 × max(m, n) = O(m + n)
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Variables
a
,b
for direction iteration - Variables
i
,j
for current position tracking - Variable
cnt
for counting cells traversed - No additional data structures that scale with input size are used
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly handling the initial position
A common mistake is forgetting that the move position (rMove, cMove)
starts as an empty cell (.
) and should be treated as if it already contains our color piece when checking for good lines. Some implementations might incorrectly check board[rMove][cMove]
or try to validate it before conceptually placing the piece.
Wrong approach:
# Checking if the initial position is valid before "placing" the piece if board[rMove][cMove] != '.': return False
Correct approach:
The solution correctly starts traversing from the next position after (rMove, cMove)
by immediately moving in the chosen direction before checking the board state.
2. Off-by-one error in counting pieces
Another pitfall is miscounting the number of pieces in the line. The good line must have at least 3 pieces total, including both endpoints. Some might forget to count the initial position or misunderstand when to check cnt > 1
.
Wrong approach:
# Checking cnt >= 1 instead of cnt > 1 if cnt >= 1 and board[current_row][current_col] == color: return True
This would incorrectly validate a line of only 2 pieces (the placed piece and an adjacent piece of the same color).
Correct approach:
The solution uses cnt > 1
, ensuring that when we find a piece of our color, we've traversed through at least one cell after the initial position, guaranteeing a minimum line length of 3.
3. Not breaking early when encountering invalid cells
Some implementations might continue checking even after encountering an empty cell or a same-colored piece too early, leading to incorrect validation of lines with gaps.
Wrong approach:
# Only breaking on empty cells, not on early same-color pieces if board[current_row][current_col] == '.': break # Missing the case where we hit our color when cnt <= 1
Correct approach:
The solution correctly breaks when encountering either an empty cell OR our own color when cnt <= 1
, ensuring no gaps in the line and preventing invalid 2-piece lines.
Which type of traversal does breadth first search do?
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!