如何找到任何规模的井字游戏的赢家?

时间:2010-11-16 21:05:37

标签: algorithm

这是interview question。 “你如何确定是否有人在任何规模的棋盘上赢得了一场井字游戏?”我听说算法复杂度为O(1)。是否有意义 ?任何人都可以解释算法吗?

8 个答案:

答案 0 :(得分:34)

答案就在那个页面上,但无论如何我都会解释。

算法的复杂度为O(1),用于确定给定的移动是否会赢得游戏。它通常不能是O(1)因为您需要知道董事会的状态以确定胜利者。但是,您可以逐步构建该状态,以便确定移动是否在O(1)中获胜。

首先,为每个玩家的每一行,每列和每个对角线设置一个数字数组。在每次移动中,增加与该移动影响的行,列和对角线(移动可能不一定在对角线上)的玩家对应的元素。如果该玩家的计数等于棋盘的尺寸,则该玩家获胜。

答案 1 :(得分:19)

检测胜利条件的最快方法是跟踪所有行,列,对角线和反对角线分数。

假设您有3x3网格。创建大小为2 * 3 + 2的分数数组,其中包含以下 [row1,row2,row3,col1,col2,col3,diag1,diag2] 的分数。当然不要忘记用0初始化它。

接下来每次移动后,你为玩家1添加+1,或者为玩家2减去-1,如下所示。

得分[row] + = point; //其中point为+1或-1

得分[gridSize + col] + =指向;

if(row == col)得分[2 * gridSize] + =点数;

if(gridSize - 1 - col == row)得分[2 * gridSize + 1] + = point;

然后你所要做的就是迭代得分数组一次并检测+3或-3(GRID_SIZE或-GRID_SIZE)。

我知道代码会说更多的话,所以这里是PHP的原型。这是非常直接的,所以我认为你不会在将其翻译成其他语言时遇到问题。

<?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());

希望有所帮助;]

答案 2 :(得分:5)

这个问题和一堆相关问题可以在O(1)时间内解决,假设存在至少3^(n^2)个存储区并假设可以预先计算查找表。此解决方案不需要先前的状态跟踪,正如其他答案所描述的那样,并且算法的运行时部分不需要对列或行进行求和,正如其他答案所描述的那样。

将n * n板状态视为单个整数B.为此,将位置(x,y)处的单个单元c表示为整数,其中c(x,y) = 0表示O,{{3} } = 1表示X,c(x,y) = 2表示空单元格。

接下来,将每个方格c(x,y)表示为:

S(x,y): 1<=x<=n, 1<=y<=n here

然后,将整个董事会状态B表示为:

S(x,y)=c(x,y) dot 3^(n(x-1)+(y-1)

假设您已经如此代表您的董事会,您可以在预先计算的表格中查看记忆位置B,该表格描述了给出问题的答案。

我提供的编码可以紧凑地表示任何n * n tic-tac-toe板配置,包括在正常播放中无法到达的位置。但是,您可以使用任何您喜欢的独特电路板编码方法,例如字符串或数组,只要您将电路板表示解释为一个长的,唯一的整数,索引到预先计算的解决方案表中。我在这里提供了一个这样的enter image description here,但还存在许多其他的。

这个提供的董事会代表也允许玩家获得任意数量的免费初始移动的类似障碍。

有趣的是,如果你有足够的记忆力,你也可以在这一点上查找问题的答案,例如当前游戏是否通过完美游戏获胜或失败,哪个移动是理想的位置,如果游戏是保证赢或输,赢或输有多少最大移动。重要的是,这种技术用于计算机国际象棋;每个人使用的查找表称为perfect hash function

将tic-tac-toe推广到任何规模的棋盘,其中连续获得k石的玩家获胜,被称为Nalimov tablebase,并且有许多关于此类游戏的有趣证据。

TL:博士;如果您正在寻找速度记录,那么几乎不可能击败低位查询表。

答案 3 :(得分:2)

我在编程采访中也被问到了这个问题。 “鉴于一个井字游戏板,如何检查此举是在CONSTANT时间内取得的胜利”

它花了我超过20分钟,但我认为能够找到答案并在O(1)中解决它

所以说让我们从一个简单的3×3 tic - tac toe board开始,在板上放一个对应于每个块的数字 123 456 789

所以我对这个问题的回答非常简单,将所有获胜组合HASH分成哈希表,如123,456,789,159等......

有两个数字列表来跟踪单个玩家的移动

alg如下所述

    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

所以我认为那是O(1)

答案 4 :(得分:1)

我写过a blog post for this question

要点是你跟踪游戏进度中每行/每列放置了多少X + 2对角线。

然后每当玩家完成转弯时,你会检查最后一个坐标的行和列是否包含N个X.如果是,那么玩家就赢了。

答案 5 :(得分:1)

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;
    }
}

答案 6 :(得分:1)

解决 n*n 井字游戏,测试获胜者。

n*2+2 组合赢得任何游戏,其中 x 或 o 必须连续 n

我们知道,如果玩家选择边缘或对角线上的任何一个方格,他们有三种获胜的机会,如果(n 为奇数,中间一个将有 5 种获胜方式(概率最高),其余任何两个方格都有两种获胜方式) .

**为了让玩家获胜,他们必须在对角线上至少选择一个位置(这只能从 n^2 个可能的结果中给我们 (n2+2) 个。 让我们记录 (n2+2)

在单独的获胜表中

假设 x 先选择第一行,o 选择第一行 n 将被记录在获胜表中

第一行(侧面、向下、对角线) 侧面意味着(第一行第一,第一行第二......到第一行第n个框) 向下表示(第一行第一,第二行第一......到第n行第一个框) 对角线表示(第一行第一,第二行第二……到第n行第n)

x....o(现在你可以从下一轮忽略这一行,因为赢 x 或 y 需要 n 个连续的 x 或 o)

x

x

第二排(侧面,向下)

Side 表示(第二行第一,第二行第二......到第二行第n个框) 向下表示(第一行第二,第二行第二......到第n行第二)

.

.

第三排(侧面,向下)

.

.

第N行(侧面,向上) Up(nth row nth, nth-1row nth....first row nth)

.

o

第一行第n(对角线) 这个是(第一排第n,第二排第n-1….第n排第一)

o

第二轮如果x选择第二行第二,0选择第二行第三,

第一行(边、下、对角线)

x….o

x

x

第二排(侧面,向下)

.ox(现在你可以在下一轮忽略这一行)

.

第三排(侧面,向下) .

.

第N行(侧面,向上)

.

.....o

第N行(对角线)

.....o

重复这一轮(第一个填满上一行的人获胜) 即(第一行(边),我们不必从第二轮检查这个,因为它有 x 或 o。

答案 7 :(得分:-2)

    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]--;
    }
}