Files
codetutor/backend/data/questions/check-if-move-is-legal.yaml

215 lines
12 KiB
YAML

title: Check if Move is Legal
slug: check-if-move-is-legal
difficulty: medium
leetcode_id: 1958
leetcode_url: https://leetcode.com/problems/check-if-move-is-legal/
categories:
- arrays
patterns:
- slug: matrix-traversal
is_optimal: true
function_signature: "def check_move(board: list[list[str]], r_move: int, c_move: int, color: str) -> bool:"
test_cases:
visible:
- input: { board: [[".", ".", ".", "B", ".", ".", ".", "."], [".", ".", ".", "W", ".", ".", ".", "."], [".", ".", ".", "W", ".", ".", ".", "."], [".", ".", ".", "W", ".", ".", ".", "."], ["W", "B", "B", ".", "W", "W", "W", "B"], [".", ".", ".", "B", ".", ".", ".", "."], [".", ".", ".", "B", ".", ".", ".", "."], [".", ".", ".", "W", ".", ".", ".", "."]], r_move: 4, c_move: 3, color: "B" }
expected: true
- input: { board: [[".", ".", ".", ".", ".", ".", ".", "."], [".", "B", ".", ".", "W", ".", ".", "."], [".", ".", "W", ".", ".", ".", ".", "."], [".", ".", ".", "W", "B", ".", ".", "."], [".", ".", ".", ".", ".", ".", ".", "."], [".", ".", ".", ".", "B", "W", ".", "."], [".", ".", ".", ".", ".", ".", "W", "."], [".", ".", ".", ".", ".", ".", ".", "B"]], r_move: 4, c_move: 4, color: "W" }
expected: false
hidden:
- input: { board: [[".", ".", "."], [".", "W", "."], [".", ".", "."]], r_move: 0, c_move: 1, color: "B" }
expected: false
- input: { board: [["B", "W", "."], [".", ".", "."], [".", ".", "."]], r_move: 0, c_move: 2, color: "B" }
expected: true
- input: { board: [[".", "W", "B"], [".", "W", "."], [".", ".", "."]], r_move: 2, c_move: 1, color: "B" }
expected: true
- input: { board: [["B", ".", "."], [".", "W", "."], [".", ".", "."]], r_move: 2, c_move: 2, color: "B" }
expected: true
description: |
You are given a **0-indexed** `8 x 8` grid `board`, where `board[r][c]` represents the cell `(r, c)` on a game board. On the board, free cells are represented by `'.'`, white cells are represented by `'W'`, and black cells are represented by `'B'`.
Each move in this game consists of choosing a free cell and changing it to the color you are playing as (either white or black). However, a move is only **legal** if, after changing it, the cell becomes the **endpoint of a good line** (horizontal, vertical, or diagonal).
A **good line** is a line of **three or more cells (including the endpoints)** where the endpoints of the line are **one color**, and the remaining cells in the middle are the **opposite color** (no cells in the line are free).
Given two integers `rMove` and `cMove` and a character `color` representing the color you are playing as (white or black), return `true` *if changing cell* `(rMove, cMove)` *to color* `color` *is a legal move, or* `false` *if it is not legal*.
constraints: |
- `board.length == board[r].length == 8`
- `0 <= rMove, cMove < 8`
- `board[rMove][cMove] == '.'`
- `color` is either `'B'` or `'W'`
examples:
- input: 'board = [[".",".",".","B",".",".",".","."],[".",".",".",".W",".",".",".","."],[".",".",".","W",".",".",".","."],[".",".",".","W",".",".",".","."],["W","B","B",".","W","W","W","B"],[".",".",".","B",".",".",".","."],[".",".",".","B",".",".",".","."],[".",".",".","W",".",".",".","."]], rMove = 4, cMove = 3, color = "B"'
output: "true"
explanation: "Placing 'B' at (4, 3) creates two good lines: a vertical line from (0, 3) to (4, 3) with 'B' endpoints and 'W' in the middle, and a horizontal line from (4, 0) to (4, 3) with endpoints at different colors satisfying the good line condition."
- input: 'board = [[".",".",".",".",".",".",".","."],[".",".B",".",".","W",".",".","."],[".",".",".W",".",".",".",".","."],[".",".",".",".W","B",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".","B","W",".","."],[".",".",".",".",".",".","W","."],[".",".",".",".",".",".",".","B"]], rMove = 4, cMove = 4, color = "W"'
output: "false"
explanation: "While there are good lines with the chosen cell as a middle cell (on the diagonal), there are no good lines with the chosen cell as an endpoint. A good line requires the placed cell to be one of the two endpoints."
explanation:
intuition: |
Think of this problem like the classic board game **Othello (Reversi)**. When you place a piece, you're looking for lines where your color "sandwiches" the opponent's pieces.
Imagine standing at the cell where you want to place your piece and looking outward in all 8 directions (up, down, left, right, and the four diagonals). For each direction, you're asking: "If I walk this way, do I first see a sequence of opponent pieces, and then eventually see one of my own pieces?"
The key insight is that a "good line" has a very specific structure:
- It starts with your color (the cell you're placing)
- It has one or more cells of the opposite color in the middle
- It ends with your color (an existing piece on the board)
The line must be at least 3 cells long (your placement + at least one opponent piece + your existing piece). This means we need to explore each direction and verify this pattern exists.
approach: |
We use **directional exploration** from the target cell:
**Step 1: Define the 8 directions**
- Use direction vectors: `(-1, 0)`, `(1, 0)`, `(0, -1)`, `(0, 1)` for cardinal directions
- Use `(-1, -1)`, `(-1, 1)`, `(1, -1)`, `(1, 1)` for diagonals
- Each vector `(dr, dc)` tells us how to step in that direction
&nbsp;
**Step 2: For each direction, check for a good line**
- Start from `(rMove, cMove)` and move in the direction `(dr, dc)`
- First, we must encounter at least one cell of the **opposite color**
- Then, we must eventually find a cell of **our color**
- If we hit the board boundary or an empty cell `'.'` before finding our color, this direction fails
&nbsp;
**Step 3: Count cells and validate**
- Track the length of the line as we traverse
- A valid good line needs: our placed piece + at least 1 opposite color + our color = minimum 3 cells
- If we find at least one opposite color cell followed by our color, we have a good line
&nbsp;
**Step 4: Return result**
- If any of the 8 directions produces a valid good line, return `true`
- If none do, return `false`
common_pitfalls:
- title: Forgetting the Minimum Length Requirement
description: |
A good line must have **at least 3 cells**. Some implementations forget this and return `true` when they find just the opposite color without confirming there's at least one opponent piece in between.
For example, if your piece is adjacent to another piece of your color with no opponent pieces in between, that's NOT a good line — it's just two adjacent same-colored pieces.
wrong_approach: "Return true as soon as you find your color in any direction"
correct_approach: "Ensure at least one opposite-color cell exists between the endpoints"
- title: Not Checking All 8 Directions
description: |
Good lines can be horizontal, vertical, or diagonal. Missing any of the 8 directions means you might return `false` when a valid line exists in an unchecked direction.
The diagonal directions are often forgotten: `(-1, -1)`, `(-1, 1)`, `(1, -1)`, `(1, 1)`.
wrong_approach: "Only check horizontal and vertical (4 directions)"
correct_approach: "Check all 8 directions including diagonals"
- title: Incorrect Boundary Handling
description: |
When exploring a direction, you might step outside the 8x8 grid. Accessing `board[-1][c]` or `board[r][8]` will cause index errors or incorrect results.
Always validate that `0 <= r < 8` and `0 <= c < 8` before accessing the board.
wrong_approach: "Access board cells without bounds checking"
correct_approach: "Check bounds before each cell access"
- title: Stopping at Empty Cells
description: |
If you encounter an empty cell `'.'` while searching for the endpoint, the line is broken. A good line cannot have any free cells — only the two endpoint colors and the opposite color in between.
For example: `B . W B` is NOT a good line because of the empty cell.
wrong_approach: "Skip over empty cells while searching"
correct_approach: "Return false for this direction when hitting an empty cell"
key_takeaways:
- "**Direction vectors simplify grid traversal**: Using `(dr, dc)` pairs lets you handle all 8 directions with the same code logic"
- "**Othello/Reversi pattern**: This problem tests your ability to validate sandwich patterns — a common theme in board game problems"
- "**Early termination**: Once you find one valid good line, you can return `true` immediately without checking remaining directions"
- "**Boundary awareness**: Grid problems require careful bounds checking to avoid index errors"
time_complexity: "O(1). The board is always 8x8, and we check at most 8 directions with at most 7 steps each. This is constant time regardless of input."
space_complexity: "O(1). We only use a few variables for direction vectors and loop counters. No additional data structures are needed."
solutions:
- approach_name: Direction Exploration
is_optimal: true
code: |
def check_move(board: list[list[str]], rMove: int, cMove: int, color: str) -> bool:
# All 8 directions: up, down, left, right, and 4 diagonals
directions = [(-1, 0), (1, 0), (0, -1), (0, 1),
(-1, -1), (-1, 1), (1, -1), (1, 1)]
# Determine the opposite color
opposite = 'W' if color == 'B' else 'B'
# Check each direction for a valid good line
for dr, dc in directions:
# Start one step away from the placed cell
r, c = rMove + dr, cMove + dc
count = 0 # Count of opposite-color cells
# Walk in this direction while we see opposite color
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opposite:
count += 1
r += dr
c += dc
# After the loop, check if we ended on our color
# and had at least one opposite cell in between
if count >= 1 and 0 <= r < 8 and 0 <= c < 8 and board[r][c] == color:
return True
return False
explanation: |
**Time Complexity:** O(1) — The board is fixed at 8x8, so we check at most 8 directions with at most 7 cells each.
**Space Complexity:** O(1) — Only a few variables for iteration, no extra data structures.
We systematically check each of the 8 directions from the target cell. For each direction, we count consecutive opposite-color cells and verify the line terminates with our color. The moment we find a valid good line, we return `true`.
- approach_name: Helper Function per Direction
is_optimal: true
code: |
def check_move(board: list[list[str]], rMove: int, cMove: int, color: str) -> bool:
def is_good_line(dr: int, dc: int) -> bool:
"""Check if there's a good line in direction (dr, dc)."""
opposite = 'W' if color == 'B' else 'B'
r, c = rMove + dr, cMove + dc
length = 1 # Start counting from placed cell
# Skip over opposite-color cells
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opposite:
length += 1
r += dr
c += dc
# Valid if: we moved at least once, in bounds, and ends with our color
# length >= 3 means: our cell + at least 1 opposite + their cell
if length >= 3 and 0 <= r < 8 and 0 <= c < 8 and board[r][c] == color:
return True
return False
# Check all 8 directions
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue # Skip the "no movement" case
if is_good_line(dr, dc):
return True
return False
explanation: |
**Time Complexity:** O(1) — Same as above, fixed board size.
**Space Complexity:** O(1) — The helper function uses only local variables.
This approach extracts the direction-checking logic into a helper function, making the code more readable. We generate all 8 directions using nested loops over `[-1, 0, 1]` and skip the `(0, 0)` case. The helper function encapsulates the line validation logic cleanly.