This is an interview question. "How would you determine if someone has won a game of tic-tac-toe on a board of any size?" I heard the algorithm complexity wa开发者_开发知识库s O(1). Does it make sense ? Can anybody explain the algorithm ?
The answer is right on that page, but I'll explain it anyway.
The algorithm's complexity is O(1) for determining if a given move will win the game. It cannot be O(1) in general since you need to know the state of the board to determine a winner. However, you can build that state incrementally such that you can determine whether a move wins in O(1).
To start, have an array of numbers for each row, column, and diagonal for each player. On each move, increment the elements corresponding to the player for the row, column, and diagonal (the move may not necessarily be on a diagonal) influenced by that move. If the count for that player is equal to the dimension of the board, that player wins.
Fastest way of detecting win condition is to keep track of all rows, cols, diagonal and anti-diagonal scores.
Let's say you have 3x3 grid. Create score array of size 2*3+2 that will hold scores as following [row1, row2, row3, col1, col2, col3, diag1, diag2]. Of course don't forget to initialize it with 0.
Next after every move you add +1 for player 1 or subtract -1 for player 2 as following.
score[row] += point; // where point is either +1 or -1
score[gridSize + col] += point;
if (row == col) score[2*gridSize] += point;
if (gridSize - 1 - col == row) score[2*gridSize + 1] += point;
Then all you have to do is iterate over score array once and detect +3 or -3 (GRID_SIZE or -GRID_SIZE).
I know code tells more then words so here is a prototype in PHP. It's pretty straight forward so I don't think you'll have problems translating it into other lang.
<?php
class TicTacToe {
const PLAYER1 = 'X';
const PLAYER1_POINT = 1;
const PLAYER2 = 'O';
const PLAYER2_POINT = -1; // must be the opposite of PLAYER1_POINT
const BLANK = '';
/**
* Level size.
*/
private $gridSize;
/**
* Level data.
* Two dimensional array of size GRID_SIZE x GRID_SIZE.
* Each player move is stored as either 'X' or 'O'
*/
private $grid;
/**
* Score array that tracks score for rows, cols and diagonals.
* e.g. for 3x3 grid [row1, row2, row3, col1, col2, col3, diag1, diag2]
*/
private $score;
/**
* Avaialable moves count for current game.
*/
private $movesLeft = 0;
/**
* Winner of the game.
*/
private $winner = null;
public function __construct($size = 3) {
$this->gridSize = $size;
$this->grid = array();
for ($i = 0; $i < $this->gridSize; ++$i) {
$this->grid[$i] = array_fill(0, $this->gridSize, self::BLANK);
}
$this->score = array_fill(0, 2*$this->gridSize + 2, 0);
$this->movesLeft = $this->gridSize * $this->gridSize;
$this->winner = null;
}
public function getWinner() {
return $this->winner;
}
public function displayGrid() {
$this->display("--------\n");
for ($row = 0; $row < $this->gridSize; ++$row) {
for ($col = 0; $col < $this->gridSize; ++$col) {
if (self::BLANK == $this->grid[$row][$col]) $this->display(' ');
else $this->display($this->grid[$row][$col].' ');
}
$this->display("\n");
}
$this->display("--------\n");
}
public function play(MoveInput $input) {
$this->display("NEW GAME\n");
$nextPlayer = self::PLAYER1;
$done = false;
while(!$done) {
$move = $input->getNextMove($nextPlayer);
if (NULL == $move) {
$this->display("ERROR! NO MORE MOVES\n");
break;
}
$error = false;
$this->makeMove($move['player'], $move['row'], $move['col'], $error);
if ($error) {
$this->display("INVALID MOVE! Please try again.\n");
continue;
}
$nextPlayer = ($nextPlayer == self::PLAYER1) ? self::PLAYER2 : self::PLAYER1;
$this->displayGrid();
$done = $this->checkScore();
}
}
private function makeMove($player, $row, $col, &$error) {
$error = false;
if (!$this->isValidMove($row, $col) || self::BLANK != $this->grid[$row][$col]) {
$error = true;
return;
}
$this->grid[$row][$col] = $player;
--$this->movesLeft;
$point = 0;
if (self::PLAYER1 == $player) $point = self::PLAYER1_POINT;
if (self::PLAYER2 == $player) $point = self::PLAYER2_POINT;
$this->score[$row] += $point;
$this->score[$this->gridSize + $col] += $point;
if ($row == $col) $this->score[2*$this->gridSize] += $point;
if ($this->gridSize - 1 - $col == $row) $this->score[2*$this->gridSize + 1] += $point;
}
private function checkScore() {
if (0 == $this->movesLeft) {
$this->display("game is a DRAW\n");
return true;
}
for ($i = 0; $i < count($this->score); ++$i) {
if ($this->player1TargetScore() == $this->score[$i]) {
$this->display("player 1 WIN\n");
$this->winner = self::PLAYER1;
return true;
}
if ($this->player2TargetScore() == $this->score[$i]) {
$this->display("player 2 WIN\n");
$this->winner = self::PLAYER2;
return true;
}
}
return false;
}
private function isValidMove($row, $col) {
return (0 <= $row && $row < $this->gridSize) &&
(0 <= $col && $col < $this->gridSize);
}
private function player1TargetScore() {
return $this->gridSize * self::PLAYER1_POINT;
}
private function player2TargetScore() {
return $this->gridSize * self::PLAYER2_POINT;
}
private function display($string) {
echo $string;
}
}
interface MoveInput {
public function getNextMove($player);
}
class QueuedMoveInput implements MoveInput {
private $moves;
public function __construct($movesArray) {
$this->moves = $movesArray;
}
public function getNextMove($player) {
return array_shift($this->moves);
}
}
class InteractiveMoveInput implements MoveInput {
public function getNextMove($player) {
while(true) {
echo "Please enter next move for player $player: [row,col] ";
$line = trim(fgets(STDIN));
list($row, $col) = sscanf($line, "%D,%D");
if (is_numeric($row) && is_numeric($col)) {
return array('player' => $player, 'row' => $row, 'col' => $col);
}
}
}
}
// helpers
function p1($row, $col) {
return array('player' => TicTacToe::PLAYER1, 'row' => $row, 'col' => $col);
}
function p2($row, $col) {
return array('player' => TicTacToe::PLAYER2, 'row' => $row, 'col' => $col);
}
// TESTING!!!!! ;]
// GAME 1 - testing diagonal (0,0) -> (2,2) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(1,1), p2(0,1), p1(2,0), p2(0,2), p1(0,0), p2(1,0), p1(2,2), p2(2,1)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);
// GAME 2 - using invalid move, testing straight line (0,0) -> (0,2) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(1,1), p2(1,1), p2(2,0), p1(2,1), p2(0,1), p1(2,2), p2(0,0), p1(1,0), p2(0,2)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER2);
// GAME 3 - testing draw condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(1,1), p2(2, 2), p1(1,2), p2(1,0), p1(2,0), p2(0,2), p1(0,1), p2(2,1), p1(0,0)));
$game->play($moves);
assert($game->getWinner() == NULL);
// GAME 4 - testing diagonal (2,0) -> (0,2) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(2,0), p2(1, 2), p1(0,2), p2(2,2), p1(0,1), p2(0,0), p1(1,1)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);
// GAME 5 - testing straight line (0,0) -> (2,0) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p2(1,1), p1(0,0), p2(0,2), p1(2,0), p2(2,1), p1(1,0)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);
// GAME 6 - 5x5 grid, testing diagonal (0,0) -> (4,4) win condition
$game = new TicTacToe(5);
$moves = new QueuedMoveInput(array(p1(1,1), p2(0,1), p1(2,0), p2(0,2), p1(0,0), p2(1,0), p1(2,2), p2(4,2), p1(3,3), p2(4,3), p1(4,4)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);
// GAME 7 - Interactive game.
$game = new TicTacToe();
$game->play(new InteractiveMoveInput());
Hope that helps ;]
This problem, and a bunch of related problems, may be solved in O(1) time, assuming that at least
memory regions exist and assuming that a lookup table can be precomputed. This solution does not require previous state tracking, as other answers describe, and the run-time portion of the algorithm doesn't require summing columns or rows, as other answers describe.Treat the n * n board state as a single integer B. To do this, represent a single cell c at position (x,y) as an integer where
= 0 indicates O, = 1 indicates X, and = 2 indicates an empty cell.Next, represent each square
as:Then, represent the entire board state B as:
Assuming you have represented your board thusly, you can look at memory position B within a pre-computed table that describes the answer to the question given.
The encoding I've provided can represent any n * n tic-tac-toe board configuration compactly, including positions that cannot be arrived at in normal play. However, you can use any unique board encoding method you like, such as strings or arrays, so long as you interpret the board representation as a long, unique integer, indexing into a table of precomputed solutions. I've provided one such perfect hash function here but many others exist.
This board representation provided also permits go-like handicaps where a player is granted an arbitrary number of free initial moves.
Interestingly, if you have sufficient memory, you may also at this point look up answers to questions such as whether the current game is won or lost with perfect play, which move is ideal from the position, and if the game is a guaranteed win or loss, how many maximum moves exist to the win or loss. This technique is used, importantly, in computer chess; the lookup table everyone uses is referred to as the Nalimov tablebase.
The generalization of tic-tac-toe into any size board, where the player who gets k stones in a row wins, is called the m,n,k game and there are many interesting proofs about this type of game.
tl:dr; if you're going for a speed record, it's nearly impossible to beat the lowly lookup table.
I just got asked this question in a programming interview as well. " Given a tic-tac-toe board, how to check the move was a winning move in CONSTANT time"
it took me well over 20 minutes, but I THINK was able to find the answer and solve it in O(1)
So say let's start with a simple 3 by 3 tic - tac toe board, put a number corresponding to each block on the board 123 456 789
So my answer to the question is pretty simple, HASH all the winning combinations into a Hash table, such as 123, 456, 789, 159 etc...
Have two lists of numbers to keep track of the individual player's move
The alg is described below
So when player_1 puts a X on a square, he will get the corresponding
number into his number list
When player_2 puts a O on a square, he will also get the corresponding
number into his number list.
At the end of every round, look through the Hash table to see if any
of the two players have the winning combination
So I think that's O(1)
I have wrote a blog post for this question .
The gist is that you keep track of how many X have been placed in each row/columns + 2 diagonals as the game progress.
Then every time when player finish their turn, you check if the row and column of the last coordinate placed contain N number of X. If it is, then the player has won.
class Solution {
public String tictactoe(int[][] moves) {
boolean startValidating = false;
int[][] matrix = new int[3][3]; //if you want to try for 4 by 4 change to [4][5]
for (int i = 0; i < moves.length; i++) {
if (i > (matrix.length - 2) * 2 + 1) { //start validating only after after 5th move in 3x3, since no way there is a possibility to win, this improve efficiency by unnecessary validation untile first 4 moves
startValidating = true;
}
if (i % 2 == 0) { //even case 1st candidate starts
matrix[moves[i][0]][moves[i][1]] = 'x';
if (startValidating) {
if (checkAllRowSame(matrix) || checkAllColumnSame(matrix) || checkAllDiagonalSame(matrix))
//check all row, all column, all diagonal tic tac toe possibility win for first candidate A
return Character.toString('A');
}
} else { //odd case second candidate starts
matrix[moves[i][0]][moves[i][1]] = '0';
if (startValidating) {
if (checkAllRowSame(matrix) || checkAllColumnSame(matrix) || checkAllDiagonalSame(matrix))
//check all row, all column, all diagonal tic tac toe possibility win for first candidate B
return Character.toString('B');
}
}
}
if (moves.length == matrix.length * matrix.length) //if all the moves completed, there is no one win, it becomes draw
return "Draw";
return "Pending"; //if less number of moves, unable to decide winner
}
private static boolean checkAllRowSame(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
boolean flag = true;
for (int j = 0; j < matrix.length - 1; j++) {
if ((!(matrix[i][j] == 0 || matrix[i][j + 1] == 0)) && flag) { //skip if any one of them is not filled
flag = flag && (matrix[i][j] == matrix[i][j + 1]); // set to false in case of not equal, proceed to next row validation by skipping remaining items in the row
} else {
flag = false;
break;
}
}
if (flag)
return true;
}
return false;
}
private static boolean checkAllColumnSame(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
boolean flag = true;
for (int j = 0; j < matrix.length - 1; j++) {
if ((!(matrix[j][i] == 0 || matrix[j + 1][i] == 0)) && flag) { //skip if any one of them is not filled
flag = flag && (matrix[j][i] == matrix[j + 1][i]); // set to false in case of not equal, proceed to next col validation by skipping remaining items in the col
} else {
flag = false;
break;
}
}
if (flag)
return true;
}
return false;
}
private static boolean checkAllDiagonalSame(int[][] matrix) {
boolean flag = true;
for (int i = 0; i < matrix.length - 1; i++) {
if ((!(matrix[i][i] == 0 || matrix[i + 1][i + 1] == 0)) && flag) { //skip if any one of them is not filled
flag = flag && (matrix[i][i] == matrix[i + 1][i + 1]); // set to false in case of not equal, proceed to right to left diagonal in the below for loop
} else {
flag = false;
break;
}
if (!flag)
break;
}
if (flag)
return true;
flag = true;
for (int i = 0, j = matrix.length - 1; i < matrix.length - 1 && j > 0; i++, j--) {
if ((!(matrix[i][j] == 0 || matrix[i + 1][j - 1] == 0)) && flag) {
flag = flag && (matrix[i][j] == matrix[i + 1][j - 1]);
} else {
flag = false;
break;
}
if (!flag)
break;
}
if (flag)
return true;
return flag;
}
}
solve n*n tic-tac-toe, test for a winner .
n*2+2 combination to win any game, where x or o has to be n consecutive
We know that if the player select any square in edge or diagonal they have chance of winning in three ways , if (n is odd the middle one will have 5ways (highest probability) , rest any two square has two ways of winning.
**For a player to win , they have to select at least one position in diagonal ( this gives us only (n2+2) out of n^2 possible outcomes . Let’s keep record of (n2+2)
In a separate win sheet
Let’s say x select first row first , o select first row n will be recorded in a win sheet
First row (side,down, diagonal) Side means( first row first , first row second …..to first row nth box) Down means (first row first, second row first …to nth row first box) Diagonal means (first row first , second row second…to nth row nth)
x….o (now you can ignore this row from next round as for a win x or y needs n consecutive x or o)
x
x
Second row (side,down)
Side means( second row first , second row second …..to second row nth box) Down means (first row second , second row second …to nth row second)
.
.
Third row(side,down)
.
.
Nth row(side,up) Up(nth row nth, nth-1row nth….first row nth)
.
o
First row nth(diagonal) This one is (first row nth, second row nth-1….nth row first)
o
Second round if x select second row second and 0 select second row third,
First row (side,down, diagonal)
x….o
x
x
Second row (side,down)
.ox (now you can ignore this row from next round )
.
Third row(side,down) .
.
Nth row(side,up)
.
…..o
Nth row first(diagonal)
…..o
Repeat the round (the first to fill the above row wins) ie( first row (side), we don’t have to check this from second round as it has both x or o.
class TicTacToe
{
//http://basicalgos.blogspot.com/#!/2012/03/13-test-winning-condition-of-tic-tac.html
//No time for test case
int[,] board = null;
int diag = 0;
int antiDiag = 0;
int[] rows = null;
int[] cols = null;
int diagonalLen = 0;
public TicTacToe(int rowLen, int colLen)
{
board = new int[rowLen, colLen];
rows = new int[rowLen];
cols = new int[colLen];
if (rowLen == colLen)
diag = (int)(rowLen * Math.Sqrt(2));//Square formula
else
diagonalLen = (int)Math.Sqrt(Math.Pow(rowLen, 2) + Math.Pow(colLen, 2));//rectangle formula
}
public bool hasWon(int rowIdx, int colIdx, int player)
{
if (player == 1)
{
rows[rowIdx]++;
cols[colIdx]++;
if (rowIdx == colIdx) diag++;
if (rowIdx + colIdx == rows.Length - 1) antiDiag++;//This is IMPORTANT finding.............
}
else
{
rows[rowIdx]--;
cols[colIdx]--;
if (rowIdx == colIdx) diag--;
if (rowIdx + colIdx == rows.Length - 1) antiDiag--;
}
return diag == diagonalLen || rows[rowIdx] == rows.Length || cols[colIdx] == cols.Length || antiDiag == diagonalLen;
}
public void markOnBoard(int row, int col, int player)
{
if (player == 1)
board[row, col]++;
else
board[row, col]--;
}
}
精彩评论