Design BFS to detect forced win in Tic-Tac-Toe
Company: Databricks
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates algorithm design and search-space reasoning, focusing on BFS-based game-tree exploration, state representation, symmetry reduction and hashing, win/draw detection, pruning and move-ordering heuristics, and time/space complexity analysis in terms of n, k, t, and the branching factor within the Coding & Algorithms domain.
Constraints
- 1 <= n <= 4 and 1 <= k <= n
- 0 <= t <= 3
- board is square, contains only '.', 'X', and 'O', and the input position has at most one winner already present
Examples
Input: (["XX.", "OO.", "..."], "X", 3, 1)
Expected Output: True
Explanation: X places at the last cell of the top row to form XXX immediately.
Input: (["X..", ".O.", "..X"], "X", 3, 2)
Expected Output: True
Explanation: X can play in the top-right corner (or symmetrically bottom-left), creating two separate one-move threats. O can block only one, so X wins on its second move.
Input: (["X.O", "...", "..."], "O", 3, 0)
Expected Output: False
Explanation: O does not already have a winning line, and with t = 0 it cannot make any move.
Input: (["OOO", "X..", "..."], "X", 3, 2)
Expected Output: False
Explanation: The opponent already has a winning line, so the answer is False before any search.
Input: (["XXX", "OOO", "..."], "X", 3, 1)
Expected Output: True
Explanation: By the stated rule, if the current player already has a winning line, return True even if the opponent also has one.
Input: (["X..", ".O.", "..."], "X", 3, 1)
Expected Output: False
Explanation: X cannot complete three in a row with only one move from this position.
Input: (["."], "X", 1, 1)
Expected Output: True
Explanation: With k = 1, placing X in the only cell wins immediately.
Solution
def solution(board, player, k, t):
from collections import deque
from functools import lru_cache
n = len(board)
board = tuple(board)
opponent = 'O' if player == 'X' else 'X'
@lru_cache(None)
def has_win(state, mark):
for i in range(n):
for j in range(n):
if state[i][j] != mark:
continue
if j + k <= n and all(state[i][j + d] == mark for d in range(k)):
return True
if i + k <= n and all(state[i + d][j] == mark for d in range(k)):
return True
if i + k <= n and j + k <= n and all(state[i + d][j + d] == mark for d in range(k)):
return True
if i + k <= n and j - k + 1 >= 0 and all(state[i + d][j - d] == mark for d in range(k)):
return True
return False
if has_win(board, player):
return True
if has_win(board, opponent):
return False
if t == 0:
return False
root = (board, player, t)
queue = deque([root])
visited = {root}
order = []
children = {}
while queue:
state = queue.popleft()
order.append(state)
state_board, turn, rem = state
if has_win(state_board, player) or has_win(state_board, opponent) or rem == 0:
children[state] = []
continue
empties = []
for i in range(n):
for j, cell in enumerate(state_board[i]):
if cell == '.':
empties.append((i, j))
if not empties:
children[state] = []
continue
next_turn = opponent if turn == player else player
next_states = []
for i, j in empties:
rows = list(state_board)
row = rows[i]
rows[i] = row[:j] + turn + row[j + 1:]
child_board = tuple(rows)
child_rem = rem - 1 if turn == player else rem
child = (child_board, next_turn, child_rem)
next_states.append(child)
if child not in visited:
visited.add(child)
queue.append(child)
children[state] = next_states
value = {}
for state in reversed(order):
state_board, turn, rem = state
if has_win(state_board, player):
value[state] = True
continue
if has_win(state_board, opponent):
value[state] = False
continue
if rem == 0:
value[state] = False
continue
if not any('.' in row for row in state_board):
value[state] = False
continue
if turn == player:
value[state] = any(value[child] for child in children[state])
else:
value[state] = all(value[child] for child in children[state])
return value[root]Time complexity: O(S * n^3), where S is the number of reachable states explored within the move limit. Space complexity: O(S * n^2).
Hints
- Build the game graph level by level only up to the move limit, then evaluate it backward. On your turn use any(...); on the opponent's turn use all(...).
- A board plus whose turn it is is a state. To avoid repeated work, hash states; for extra pruning, canonicalize the board under the 8 rotations/reflections of a square.