Valid Tic-Tac-Toe State Solution In C++/Python/java/JS
Problem Description
In this problem, you are given a 3x3 Tic-Tac-Toe board represented as a string array. The goal is to determine if the given board configuration could have been reached through a sequence of valid moves according to the rules of Tic-Tac-Toe. The game follows a turn-based approach where the first player always places 'X', followed by the second player placing 'O'. Moves are made on empty squares, and once a player wins or the board is full, no further moves can be made.
Example
Input 1: board = ["O ", " ", " "]
Output: false
Explanation:
The input board is ["O ", " ", " "]\. Here, the board shows that 'O' has been placed before 'X', which violates the rule that 'X' always goes first. Hence, the output is false.
Input 2: board = ["XOX", "O O", "XOX"]
Output: true
Explanation:
The input board is ["XOX", "O O", "XOX"]. This configuration follows all the rules—players took turns correctly, and the board represents a valid end-state of a Tic-Tac-Toe game. The output is true.
Constraints
- board.length == 9
- board[i].length == 9
- board[i][j] is a digit 1-9 or '.'.
First, count the occurrences of 'X' and 'O'. Since 'X' always plays first, its count should be equal to or exactly one more than 'O'. If 'O' has more moves than 'X', the board state is invalid. Next, check if any player has won by identifying three consecutive 'X' or 'O' in a row, column, or diagonal. If 'X' has won, then 'O' should not have the same count as 'X' because 'X' should have played last. Similarly, if 'O' has won, 'X' should not have more moves, as 'O' must have played last. Additionally, if both 'X' and 'O' have winning conditions at the same time, the board is invalid. Finally, ensure that no moves have been made after a winning condition has already been reached, as no further moves should be possible once the game ends.
Brute Force Approach
Intuition
Tic-Tac-Toe is a turn-based game where players alternately place 'X' and 'O' on a 3x3 board. The challenge is to determine if a given board state could have been reached through a valid sequence of moves. A valid board must follow the rules: 'X' always plays first, players take turns, and the game ends when a player wins or the board is full. If 'X' has played more than one extra move over 'O', or if 'O' has played the same number or more moves than 'X', the board is invalid. Another crucial aspect is checking the winning conditions—if a player has already won, no further moves should have been made. Additionally, both 'X' and 'O' cannot win at the same time since the game stops as soon as a winner is determined. The problem requires verifying all these conditions, making a brute force approach suitable by iterating over the board and checking every possible violation.
Approach
To determine whether the given Tic-Tac-Toe board is valid, we first count the occurrences of 'X' and 'O'. Since 'X' always plays first, the count of 'X' should either be equal to or exactly one more than the count of 'O'. If 'O' has played the same number of times as 'X' or more, the board is immediately invalid because it means 'O' played first or took extra turns.
Next, we check for a winning condition by examining all possible ways a player can win—three in a row, three in a column, or three diagonally. If 'X' has a winning combination, then 'O' should not have the same count as 'X' since 'X' must have played last. Similarly, if 'O' has a winning combination, 'X' must not have more moves than 'O', as 'O' must have played last. If both 'X' and 'O' have winning positions simultaneously, the board is invalid because a game stops immediately when a player wins.
Finally, we ensure that no extra moves were made after a winning condition was met. If a player has already won, no further moves should have been placed, as the game ends as soon as a winner is determined. If all these conditions are satisfied, the board state is valid; otherwise, it is invalid.
Dry Run
Input:
board = ["XOX", "O O", "XOX"]
Output: true
The board represents a completed Tic-Tac-Toe game. We need to check if this board state follows all game rules, including turn order, valid moves, and winning conditions. If any rule is violated, the board is invalid; otherwise, it is valid.
Step 1: Count 'X' and 'O' Moves
First, we count the occurrences of 'X' and 'O' on the board. The total count of 'X' is 5, and the total count of 'O' is 4. Since 'X' always plays first, the count of 'X' should be equal to or exactly one more than 'O'. Here, 'X' has played one extra move, which is valid. If 'O' had the same or more moves than 'X', the board would be invalid.
Step 2: Check for a Winning Condition
Next, we check if either 'X' or 'O' has won by forming three of the same symbol in a row, column, or diagonal. Looking at the board, we see that 'O' has won in the second column (O O O). Now, we verify whether the game state remains valid. Since 'O' has won, it must have played the last move. The count of 'X' (5) is only one more than 'O' (4), which means 'O' could have completed its turn before the game ended. Also, no additional moves were made after 'O' won, which is a necessary condition for validity.
Final Output: true
All conditions for a valid Tic-Tac-Toe game have been satisfied. The turn order is correct, 'O' won in a valid way, and no extra moves were made after the game ended. Since no rules were broken, the given board state is a possible outcome of a real game, making it valid.
Code for All Languages
C++
// Valid Tic-Tac-Toe State - Brute Force Approach CPP
// Function to check if a player has won
bool checkWin(vector<string>& board, char player) {
// Check rows and columns
for (int i = 0; i < 3; i++) {
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) return true; // Row win
if (board[0][i] == player && board[1][i] == player && board[2][i] == player) return true; // Column win
}
// Check diagonals
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) return true; // Main diagonal win
if (board[0][2] == player && board[1][1] == player && board[2][0] == player) return true; // Anti-diagonal win
return false;
}
// Function to check if the board state is valid
bool isValidTicTacToe(vector<string>& board) {
int xCount = 0, oCount = 0;
// Count 'X' and 'O' occurrences
for (const string& row : board) {
for (char cell : row) {
if (cell == 'X') xCount++;
else if (cell == 'O') oCount++;
}
}
// 'X' should be equal to or one more than 'O'
if (oCount > xCount || xCount > oCount + 1) return false;
// Check if a player has won
bool xWin = checkWin(board, 'X');
bool oWin = checkWin(board, 'O');
// Both 'X' and 'O' cannot win at the same time
if (xWin && oWin) return false;
// If 'X' wins, then 'X' should have one more move than 'O'
if (xWin && xCount != oCount + 1) return false;
// If 'O' wins, then 'X' and 'O' should have the same count
if (oWin && xCount != oCount) return false;
return true;
}
Java
// Valid Tic-Tac-Toe State - Brute Force Approach Java
// Generate all possible valid Tic-Tac-Toe game states
public static void generateBoards(char[][] board, int turn, int xCount, int oCount) {
// Convert board to string representation
String boardStr = boardToString(board);
// Stop recursion if board already visited
if (validBoards.contains(boardStr)) return;
// Store this board state as valid
validBoards.add(boardStr);
// Check if the game has ended (win or full board)
if (checkWin(board, 'X') || checkWin(board, 'O') || (xCount + oCount == 9)) return;
// Try placing the next valid move
char nextMove = (turn % 2 == 0) ? 'X' : 'O'; // 'X' moves on even turns, 'O' on odd
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == ' ') { // Empty spot
board[i][j] = nextMove;
generateBoards(board, turn + 1, xCount + (nextMove == 'X' ? 1 : 0), oCount + (nextMove == 'O' ? 1 : 0));
board[i][j] = ' '; // Backtrack
}
}
}
}
// Convert 2D board to string format for comparison
private static String boardToString(char[][] board) {
StringBuilder sb = new StringBuilder();
for (char[] row : board) {
sb.append(new String(row));
}
return sb.toString();
}
// Function to check if a player has won
private static boolean checkWin(char[][] board, char player) {
for (int i = 0; i < 3; i++) {
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) return true; // Row
if (board[0][i] == player && board[1][i] == player && board[2][i] == player) return true; // Column
}
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) return true; // Main diagonal
if (board[0][2] == player && board[1][1] == player && board[2][0] == player) return true; // Anti-diagonal
return false;
}
// Function to check if the given board is valid
public static boolean isValidTicTacToe(String[] board) {
// Generate all possible valid Tic-Tac-Toe game states
char[][] emptyBoard = {
{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '}
};
generateBoards(emptyBoard, 0, 0, 0);
// Convert input board to string and check if it's in valid states
return validBoards.contains(String.join("", board));
}
Python
# Valid Tic-Tac-Toe State - Brute Force Approach Python
def generate_boards(board, turn, x_count, o_count, valid_boards):
# Convert board to string format
board_str = ''.join([''.join(row) for row in board])
# Stop if this board is already in the set
if board_str in valid_boards:
return
# Store the board as a valid state
valid_boards.add(board_str)
# Stop if the game has ended (win or full board)
if check_win(board, 'X') or check_win(board, 'O') or (x_count + o_count == 9):
return
# Determine the next move
next_move = 'X' if turn % 2 == 0 else 'O'
# Try placing the next valid move in all empty positions
for i in range(3):
for j in range(3):
if board[i][j] == ' ':
board[i][j] = next_move
generate_boards(board, turn + 1, x_count + (next_move == 'X'), o_count + (next_move == 'O'), valid_boards)
board[i][j] = ' ' # Backtrack
# Function to check if a player has won
def check_win(board, player):
for i in range(3):
if all(board[i][j] == player for j in range(3)): # Row check
return True
if all(board[j][i] == player for j in range(3)): # Column check
return True
if all(board[i][i] == player for i in range(3)): # Main diagonal
return True
if all(board[i][2 - i] == player for i in range(3)): # Anti-diagonal
return True
return False
# Function to check if the input board is valid
def is_valid_tic_tac_toe(board):
JavaScript
// Valid Tic-Tac-Toe State - Brute Force Approach JavaScript
// Function to check if a player has won
function checkWin(board, player) {
for (let i = 0; i < 3; i++) {
if (board[i][0] === player && board[i][1] === player && board[i][2] === player) return true; // Row check
if (board[0][i] === player && board[1][i] === player && board[2][i] === player) return true; // Column check
}
if (board[0][0] === player && board[1][1] === player && board[2][2] === player) return true; // Main diagonal
if (board[0][2] === player && board[1][1] === player && board[2][0] === player) return true; // Anti-diagonal
return false;
}
// Function to generate all valid Tic-Tac-Toe board states
function generateBoards(board, turn, xCount, oCount, validBoards) {
let boardStr = board.map(row => row.join("")).join("");
// If this board has already been generated, return
if (validBoards.has(boardStr)) return;
// Store the board in the set
validBoards.add(boardStr);
// Stop if the game has ended
if (checkWin(board, 'X') || checkWin(board, 'O') || (xCount + oCount === 9)) return;
// Determine the next move
let nextMove = (turn % 2 === 0) ? 'X' : 'O';
// Try placing the next valid move in all empty positions
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[i][j] === ' ') {
board[i][j] = nextMove;
generateBoards(board, turn + 1, xCount + (nextMove === 'X' ? 1 : 0), oCount + (nextMove === 'O' ? 1 : 0), validBoards);
board[i][j] = ' '; // Backtrack
}
}
}
}
// Function to check if the given Tic-Tac-Toe board is valid
function isValidTicTacToe(board) {
let validBoards = new Set();
// Create an empty board
let emptyBoard = Array.from({ length: 3 }, () => Array(3).fill(' '));
// Generate all valid Tic-Tac-Toe board states
generateBoards(emptyBoard, 0, 0, 0, validBoards);
// Convert input board into a string format and check in validBoards
return validBoards.has(board.map(row => row.join("")).join(""));
}
Time Complexity - Valid Tic-Tac-Toe State - O(N!)
The time complexity of the brute force approach is O(9!), as it involves generating all possible valid Tic-Tac-Toe board states by simulating every possible move from an empty board. Since there are 9 cells and moves alternate between 'X' and 'O', the number of possible board configurations follows a factorial growth pattern, leading to 9! = 362,880 possible game sequences in the worst case. Each move creates a new state, and the game can end when a player wins or when all cells are filled. Although checking if a given board is valid is O(1) using a precomputed set of valid states, the process of generating and storing all valid states makes the approach computationally expensive.
Space Complexity - Valid Tic-Tac-Toe State - O(N!)
The space complexity is also O(9!), as it requires storing every valid Tic-Tac-Toe board state in memory. Since every possible game configuration needs to be saved, the memory usage grows factorially with the number of moves. Additionally, the recursive depth of the function is at most 9, contributing an extra O(1) space usage for the recursive call stack. While this approach ensures correctness, it is inefficient in both time and space compared to more optimized solutions that validate board configurations in constant time without precomputing all possible states.
Bridge between Brute Force and Optimized
A bridge between the brute force and optimized approach involves reducing unnecessary computations while still following a structured validation process. Instead of generating all possible Tic-Tac-Toe states, we can directly check a given board’s validity using logical constraints derived from the game’s rules.
First, we count the occurrences of 'X' and 'O' to ensure 'X' is either equal to or exactly one more than 'O'. Then, we check if any player has already won. If 'X' has won, 'O' should not have the same count as 'X', and if 'O' has won, 'X' should not have played more moves after that. This eliminates the need to precompute all valid board states, significantly reducing the factorial time complexity to O(1) by using a few simple checks. The space complexity is also reduced to O(1) as we no longer need to store all valid states.
This approach serves as a transition between brute force and fully optimized solutions, maintaining correctness while improving efficiency.
Optimized Approach
Intuition
The optimized approach leverages the fundamental rules of Tic-Tac-Toe to validate a board state efficiently, eliminating the need to generate all possible game sequences. Instead of precomputing valid states, we analyze the given board directly by checking key conditions: the count of 'X' and 'O', the winning conditions, and the order in which moves must have occurred. This allows us to determine whether the board state is possible in constant time, making the approach highly efficient.
In a valid Tic-Tac-Toe game, players alternate turns, meaning the number of 'X' marks must be equal to or exactly one more than the number of 'O' marks. Additionally, the game cannot continue after a player has already won. If either 'X' or 'O' has a winning combination (three in a row, column, or diagonal), the board must reflect a state where no further moves were made. For example, if 'O' wins, 'X' should not have an extra move. By enforcing these constraints, we can determine whether a given board could have been reached through legal gameplay without simulating all possible moves.
Approach
First, we start by counting the number of 'X' and 'O' occurrences on the board. Since players take turns, 'X' should always be either equal to or exactly one more than 'O'. If 'O' appears more times than 'X' or if 'X' is ahead by more than one move, the board is immediately considered invalid. This ensures that the turn order was followed correctly throughout the game.
Next, we check whether any player has won by looking for three identical symbols in any row, column, or diagonal. If a player has formed a winning combination, the board must reflect a state where no more moves were made after that win. This means that if both 'X' and 'O' have winning sequences simultaneously, the board is invalid because a player must have won before the other could complete a line.
If 'X' has won, then 'O' should have one less move, as 'X' must have made the final winning move. If 'O' has won, then 'X' should not have an extra move, as the game should have ended on 'O’s' winning turn. Any deviation from these conditions indicates an impossible game state, rendering the board invalid.
Finally, if none of these violations are found, the board is considered valid. This approach works in O(1) time and requires O(1) space since we are only performing a few predefined checks on a fixed 3x3 board without storing additional data. By enforcing these simple yet effective constraints, we eliminate unnecessary computations while ensuring correctness.
Dry Run
Input:
board = ["XOX",
"O O",
"XOX"]
Output: true
True (The board represents a valid Tic-Tac-Toe state that could have been reached through legal gameplay.)
Steps:
Step 1 begins with counting the occurrences of 'X' and 'O' on the board. By iterating through the board, we determine that 'X' appears five times, while 'O' appears four times. Since the rules dictate that 'X' should either be equal to or exactly one more than 'O', this condition is satisfied, and we can proceed to the next step.
Step 2 focuses on checking if any player has won by forming three identical symbols in a row, column, or diagonal. We start by scanning the rows. The first row consists of 'XOX', which does not form a winning sequence. The second row contains 'O O', which is incomplete and does not satisfy the win condition. The third row also has 'XOX', which does not result in a win. Moving on to the columns, we analyze each one. The first column contains 'XOX', the second column has 'O O', and the third column holds 'XOX', none of which create a winning sequence. Next, we examine the diagonals. The main diagonal consists of 'X O', which is incomplete, and the anti-diagonal contains 'X O', which is also not a winning pattern. Since no winning conditions are met, we move to the final validation step.
Step 3 ensures that the board's state remains consistent with the game's rules. Since no winner has been found, the only requirement left is to verify the turn-based structure of the game. Given that we previously checked and confirmed that 'X' appears exactly one more time than 'O', no further validation is required. With all conditions satisfied, the board is determined to be a valid Tic-Tac-Toe state.
Final Output: true
The board meets all the conditions of a valid Tic-Tac-Toe state. Since the number of moves is correct and no invalid winning condition exists, we return True as the final result.
Cpp
// Valid Tic-Tac-Toe State - Optimised Approach CPP
// Function to check the validity of a tic-tac-toe game board state
bool validTicTacToe(vector<string>& board) {
// Lambda to count occurrences of a provided character ('X' or 'O') on the board
auto countCharacter = [&](char character) {
int count = 0;
for (const auto& row : board) { // Iterate through each row of the board
for (char cell : row) { // Iterate through each cell in the row
count += cell == character; // Increment count if the cell matches the character
}
}
return count;
};
// Lambda to check if a given player has won the game
auto checkWin = [&](char player) {
// Check rows and columns for a win condition
for (int i = 0; i < 3; ++i) {
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) return true;
if (board[0][i] == player && board[1][i] == player && board[2][i] == player) return true;
}
// Check both diagonals for a win condition
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) return true;
return board[0][2] == player && board[1][1] == player && board[2][0] == player;
};
// Count the number of 'X' and 'O' on the board
int countX = countCharacter('X');
int countO = countCharacter('O');
// Check the game's rules:
// Rule 1: The number of 'X's must either be equal to or one more than the number of 'O's
if (countX != countO && countX - 1 != countO) return false;
// Rule 2: If 'X' has won, there must be one more 'X' than 'O'
if (checkWin('X') && countX - 1 != countO) return false;
// Rule 3: If 'O' has won, the number of 'X's and 'O's must be equal
if (checkWin('O') && countX != countO) return false;
// If all rules are satisfied, the board state is valid
return true;
}
Java
// Valid Tic-Tac-Toe State - Optimised Approach Java
private String[] board;
// Checks if the given Tic-Tac-Toe board state is valid
public boolean validTicTacToe(String[] board) {
this.board = board;
int countX = count('X'), countO = count('O');
// 'X' goes first, so countX must be equal to or one more than countO
if (countX != countO && countX - 1 != countO) {
return false;
}
// If 'X' has won, it must have exactly one more move than 'O'
if (hasWon('X') && countX - 1 != countO) {
return false;
}
// If 'O' has won, there must be an equal number of 'X' and 'O'
return !(hasWon('O') && countX != countO);
}
// Checks if the given player has won
private boolean hasWon(char player) {
// Check all rows and columns
for (int i = 0; i < 3; ++i) {
if (board[i].charAt(0) == player && board[i].charAt(1) == player && board[i].charAt(2) == player) {
return true; // Row win
}
if (board[0].charAt(i) == player && board[1].charAt(i) == player && board[2].charAt(i) == player) {
return true; // Column win
}
}
// Check both diagonals
if (board[0].charAt(0) == player && board[1].charAt(1) == player && board[2].charAt(2) == player) {
return true; // Main diagonal win
}
return board[0].charAt(2) == player && board[1].charAt(1) == player && board[2].charAt(0) == player; // Anti-diagonal win
}
// Counts the number of times the given character appears on the board
private int count(char character) {
int count = 0;
for (String row : board) {
for (char cell : row.toCharArray()) {
if (cell == character) {
++count;
}
}
}
return count;
}
Python
# Valid Tic-Tac-Toe State - Optimised Approach Python
def validTicTacToe(self, board: List[str]) -> bool:
# Function to check if player with mark 'X' or 'O' has won
def has_winner(mark):
# Check for horizontal and vertical wins
for i in range(3):
if all(board[i][j] == mark for j in range(3)): # Row win
return True
if all(board[j][i] == mark for j in range(3)): # Column win
return True
# Check for diagonal wins
if all(board[i][i] == mark for i in range(3)): # Main diagonal
return True
return all(board[i][2 - i] == mark for i in range(3)) # Anti-diagonal
# Count occurrences of 'X' and 'O'
count_x = sum(row.count('X') for row in board)
count_o = sum(row.count('O') for row in board)
# Check for the correct number of 'X's and 'O's
if count_x != count_o and count_x - 1 != count_o:
return False
# If 'X' has won, it must have exactly one more move than 'O'
if has_winner('X') and count_x - 1 != count_o:
return False
# If 'O' has won, there must be an equal number of 'X' and 'O'
if has_winner('O') and count_x != count_o:
return False
return True
JavaScript
// Valid Tic-Tac-Toe State - Optimised Approach JavaScript
const validTicTacToe = (board) => {
// Helper function to count occurrences of 'X' or 'O' on the board.
const countOccurrences = (char) => {
return board.reduce((accumulator, currentRow) => {
return accumulator + [...currentRow].filter(c => c === char).length;
}, 0);
};
// Helper function to check if a player has won the game.
const hasWon = (char) => {
// Check rows and columns for win
for (let i = 0; i < 3; ++i) {
if (board[i][0] === char && board[i][1] === char && board[i][2] === char) {
return true;
}
if (board[0][i] === char && board[1][i] === char && board[2][i] === char) {
return true;
}
}
// Check diagonals for win
if (board[0][0] === char && board[1][1] === char && board[2][2] === char) {
return true;
}
return board[0][2] === char && board[1][1] === char && board[2][0] === char;
};
// Count occurrences of 'X' and 'O'.
const xCount = countOccurrences('X');
const oCount = countOccurrences('O');
// Ensure 'X' goes first and there's at most one more 'X' than 'O'
if (xCount !== oCount && xCount - 1 !== oCount) {
return false;
}
// Check for a win by 'X'. If 'X' has won, there must be one more 'X' than 'O'.
if (hasWon('X') && xCount - 1 !== oCount) {
return false;
}
// Check for a win by 'O'. If 'O' has won, there must be an equal number of 'X' and 'O'.
if (hasWon('O') && xCount !== oCount) {
return false;
}
// If none of the invalid conditions were met, the board is valid.
return true;
};
Time Complexity - Valid Tic-Tac-Toe State - O (1)
The function validTicTacToe primarily performs three key operations: counting occurrences of characters, checking for winning conditions, and validating the board state. The counting function iterates through all the cells in a 3×3 tic-tac-toe board, meaning it processes at most 9 elements, making it a constant time operation O(1). The function that checks for a winner goes through 3 rows, 3 columns, and 2 diagonals, making a total of 8 checks in every execution. Since each check involves comparing a fixed number of elements, this operation also runs in O(1) time. Finally, the validity checks in validTicTacToe involve a few conditional statements and function calls that all operate in constant time. Since every operation in the solution executes in a constant number of steps irrespective of the input, the overall time complexity is O(1), meaning the function runs in a fixed amount of time for any given tic-tac-toe board.
Space Complexity - Valid Tic-Tac-Toe State - O (1)
The function operates on a fixed-size 3×3 tic-tac-toe board, meaning the input size is always constant. The board is stored as an array of strings, but since the size remains fixed, it does not impact the space complexity beyond O(1). Additionally, the function does not use any extra data structures like arrays, lists, or hash maps, but only a few integer variables for counting occurrences and looping through elements. The helper functions, such as the counting function and the winner-checking function, also use only a few additional variables that do not depend on input size. Since all the extra space used remains constant regardless of the board state, the overall space complexity is O(1). This means the function consumes a fixed amount of memory regardless of the input.
Similar Questions
This problem involves checking whether a given 9x9 Sudoku board is valid by ensuring that numbers don’t repeat in rows, columns, or 3x3 sub-boxes. It is similar because both problems require validating board states based on specific game rules.
This problem asks you to determine the winner of a Tic-Tac-Toe game given a sequence of moves. It involves tracking the game state and checking for win conditions, making it closely related to the validation logic in your current problem.