所有可能的井字游戏获胜组合

问题描述 投票:0回答:8

我有一次面试被问到一个看似简单的算法问题:“写一个算法来返回井字游戏所有可能的获胜组合。”我仍然想不出一种有效的方法来处理这个问题。是否有标准算法或通用算法应该应用于我不知道的类似问题?

algorithm combinations graph-theory tic-tac-toe
8个回答
10
投票

这是实际上对于蛮力来说足够简单的问题之一,虽然您可以 使用 组合学、图论或许多其他复杂的工具来解决它,但我实际上对认识到以下事实的申请者印象深刻更简单的方法(至少对于这个问题)。

只有 39,即 19,683 种可能的组合,即

x
o
<blank>
在网格中,并非所有组合都有效。

首先,有效的游戏位置是

x
o
计数之间的差异不超过一,因为它们必须交替移动。

另外,不可能出现双方三连的状态,所以也可以打折。如果两人连成三,那么他们中的一个将在previous着法中获胜。

实际上还有另一个限制,即一方不可能在没有共同单元的情况下以两种不同的方式获胜(同样,他们会在之前的一步中获胜),这意味着:

XXX
OOO
XXX

无法实现,而:

XXX
OOX
OOX

可以。但我们实际上可以忽略这一点,因为没有公共单元格就无法在不违反“最大差一”规则的情况下以两种方式获胜,因为你需要六个单元格,而对手只有三个。

所以我会简单地使用蛮力,对于计数之间差异为零或一的每个位置,检查双方的八种获胜可能性。假设他们中只有一个人赢了,那就是合法的、获胜的游戏。


下面是 Python 中的概念证明,但首先是

time
的输出在进程上运行时将输出发送到
/dev/null
以显示它有多快:

real    0m0.169s
user    0m0.109s
sys     0m0.030s

代码:

def won(c, n):
  if c[0] == n and c[1] == n and c[2] == n: return 1
  if c[3] == n and c[4] == n and c[5] == n: return 1
  if c[6] == n and c[7] == n and c[8] == n: return 1

  if c[0] == n and c[3] == n and c[6] == n: return 1
  if c[1] == n and c[4] == n and c[7] == n: return 1
  if c[2] == n and c[5] == n and c[8] == n: return 1

  if c[0] == n and c[4] == n and c[8] == n: return 1
  if c[2] == n and c[4] == n and c[6] == n: return 1

  return 0

pc = [' ', 'x', 'o']
c = [0] * 9
for c[0] in range (3):
  for c[1] in range (3):
    for c[2] in range (3):
      for c[3] in range (3):
        for c[4] in range (3):
          for c[5] in range (3):
            for c[6] in range (3):
              for c[7] in range (3):
                for c[8] in range (3):
                  countx = sum([1 for x in c if x == 1])
                  county = sum([1 for x in c if x == 2])
                  if abs(countx-county) < 2:
                    if won(c,1) + won(c,2) == 1:
                      print " %s | %s | %s" % (pc[c[0]],pc[c[1]],pc[c[2]])
                      print "---+---+---"
                      print " %s | %s | %s" % (pc[c[3]],pc[c[4]],pc[c[5]])
                      print "---+---+---"
                      print " %s | %s | %s" % (pc[c[6]],pc[c[7]],pc[c[8]])
                      print

正如一位评论者所指出的,还有一个限制。给定棋盘的获胜者的单元格不能少于失败者,因为这意味着失败者刚刚移动,尽管事实上获胜者已经在最后一步中获胜。

我不会更改代码以考虑到这一点,但检查谁拥有最多单元格(移动的最后一个人)并确保获胜线属于他们是一件简单的事情。


3
投票

另一种方法是从八个获胜位置中的每一个开始,

xxx ---
--- xxx
--- --- ... etc.,

并递归填写所有合法组合(从插入 2 个

o
开始,然后为每个
x
添加一个
o
;避免
o
获胜位置):

xxx xxx xxx
oo- oox oox
--- o-- oox ... etc.,

1
投票

今天去苹果面试,也有同样的问题。那一刻我无法好好思考。后来,在去开会之前,我在 15 分钟内编写了组合函数,开会回来后,我又在 15 分钟内编写了验证函数。面试的时候紧张,苹果不相信我的简历,他们只相信他们在面试中看到的,我不怪他们,很多公司都一样,我只是说这个招聘过程中的某些东西看起来不太聪明.

无论如何,这是我在 Swift 4 中的解决方案,组合函数有 8 行代码,检查有效板的代码有 17 行。

干杯!!!

// Not used yet: 0
// Used with x : 1
// Used with 0 : 2
// 8 lines code to get the next combination
func increment ( _ list: inout [Int], _ base: Int ) -> Bool {
    for digit in 0..<list.count {
        list[digit] += 1
        if list[digit] < base { return true }
        list[digit] = 0
    }
    return false
}
let incrementTicTacToe = { increment(&$0, 3) }

let win0_ = [0,1,2] // [1,1,1,0,0,0,0,0,0]
let win1_ = [3,4,5] // [0,0,0,1,1,1,0,0,0]
let win2_ = [6,7,8] // [0,0,0,0,0,0,1,1,1]
let win_0 = [0,3,6] // [1,0,0,1,0,0,1,0,0]
let win_1 = [1,4,7] // [0,1,0,0,1,0,0,1,0]
let win_2 = [2,5,8] // [0,0,1,0,0,1,0,0,1]
let win00 = [0,4,8] // [1,0,0,0,1,0,0,0,1]
let win11 = [2,4,6] // [0,0,1,0,1,0,1,0,0]
let winList = [ win0_, win1_, win2_, win_0, win_1, win_2, win00, win11]
// 16 lines to check a valid board, wihtout countin lines of comment.
func winCombination (_ tictactoe: [Int]) -> Bool {
    var count = 0
    for win in winList {
        if tictactoe[win[0]] == tictactoe[win[1]],
            tictactoe[win[1]] == tictactoe[win[2]],
            tictactoe[win[2]] != 0 {
            // If the combination exist increment count by 1.
            count += 1
        }
        if count == 2 {
            return false
        }
    }
    var indexes = Array(repeating:0, count:3)
    for num in tictactoe { indexes[num] += 1 }
    // '0' and 'X' must be used the same times or with a diference of one.
    // Must one and only one valid combination
    return abs(indexes[1] - indexes[2]) <= 1 && count == 1
}
// Test
var listToIncrement = Array(repeating:0, count:9)
var combinationsCount = 1
var winCount = 0
while incrementTicTacToe(&listToIncrement) {
    if winCombination(listToIncrement) == true {
        winCount += 1
    }
    combinationsCount += 1
}
print("There is \(combinationsCount) combinations including possible and impossible ones.")
print("There is \(winCount) combinations for wining positions.")
/*
  There are 19683 combinations including possible and impossible ones.
  There are 2032 combinations for winning positions.
*/

listToIncrement = Array(repeating:0, count:9)
var listOfIncremented = ""
for _ in 0..<1000 { // Win combinations for the first 1000 combinations
    _ = incrementTicTacToe(&listToIncrement)
    if winCombination(listToIncrement) == true {
        listOfIncremented += ", \(listToIncrement)"
    }
}
print("List of combinations: \(listOfIncremented)")

/* 
  List of combinations: , [2, 2, 2, 1, 1, 0, 0, 0, 0], [1, 1, 1, 2, 2, 0, 0, 0, 0], 
  [2, 2, 2, 1, 0, 1, 0, 0, 0], [2, 2, 2, 0, 1, 1, 0, 0, 0], [2, 2, 0, 1, 1, 1, 0, 0, 0],
  [2, 0, 2, 1, 1, 1, 0, 0, 0], [0, 2, 2, 1, 1, 1, 0, 0, 0], [1, 1, 1, 2, 0, 2, 0, 0, 0],
  [1, 1, 1, 0, 2, 2, 0, 0, 0], [1, 1, 0, 2, 2, 2, 0, 0, 0], [1, 0, 1, 2, 2, 2, 0, 0, 0],
  [0, 1, 1, 2, 2, 2, 0, 0, 0], [1, 2, 2, 1, 0, 0, 1, 0, 0], [2, 2, 2, 1, 0, 0, 1, 0, 0],
  [2, 2, 1, 0, 1, 0, 1, 0, 0], [2, 2, 2, 0, 1, 0, 1, 0, 0], [2, 2, 2, 1, 1, 0, 1, 0, 0],
  [2, 0, 1, 2, 1, 0, 1, 0, 0], [0, 2, 1, 2, 1, 0, 1, 0, 0], [2, 2, 1, 2, 1, 0, 1, 0, 0],
  [1, 2, 0, 1, 2, 0, 1, 0, 0], [1, 0, 2, 1, 2, 0, 1, 0, 0], [1, 2, 2, 1, 2, 0, 1, 0, 0],
  [2, 2, 2, 0, 0, 1, 1, 0, 0]
*/

0
投票

这是一个 java 等效代码示例

包装测试;

公开课井字棋{

public static void main(String[] args) {
    // TODO Auto-generated method stub
    // 0 1 2
    // 3 4 5
    // 6 7 8
    char[] pc = {' ' ,'o', 'x' };

    char[] c = new char[9];

    // initialize c

    for (int i = 0; i < 9; i++)
        c[i] = pc[0];

    for (int i = 0; i < 3; i++) {
        c[0] = pc[i];
        for (int j = 0; j < 3; j++) {
            c[1] = pc[j];
            for (int k = 0; k < 3; k++) {
                c[2] = pc[k];
                for (int l = 0; l < 3; l++) {
                    c[3] = pc[l];
                    for (int m = 0; m < 3; m++) {
                        c[4] = pc[m];
                        for (int n = 0; n < 3; n++) {
                            c[5] = pc[n];
                            for (int o = 0; o < 3; o++) {
                                c[6] = pc[o];
                                for (int p = 0; p < 3; p++) {
                                    c[7] = pc[p];
                                    for (int q = 0; q < 3; q++) {
                                        c[8] = pc[q];

                                        int countx = 0;
                                        int county = 0;

                                        for(int r = 0 ; r<9 ; r++){
                                            if(c[r] == 'x'){

                                                countx = countx + 1;
                                            }
                                            else if(c[r] == 'o'){

                                                county = county + 1;

                                            }


                                        }

                                        if(Math.abs(countx - county) < 2){

                                            if(won(c, pc[2])+won(c, pc[1]) == 1 ){
                                                System.out.println(c[0] + " " + c[1] + " " + c[2]);
                                                System.out.println(c[3] + " " + c[4] + " " + c[5]);
                                                System.out.println(c[6] + " " + c[7] + " " + c[8]);

                                                System.out.println("*******************************************");


                                            }


                                        }









                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

public static int won(char[] c, char n) {

    if ((c[0] == n) && (c[1] == n) && (c[2] == n))
        return 1;
    else if ((c[3] == n) && (c[4] == n) && (c[5] == n))
        return 1;
    else if ((c[6] == n) && (c[7] == n) && (c[8] == n))
        return 1;
    else if ((c[0] == n) && (c[3] == n) && (c[6] == n))
        return 1;
    else if ((c[1] == n) && (c[4] == n) && (c[7] == n))
        return 1;
    else if ((c[2] == n) && (c[5] == n) && (c[8] == n))
        return 1;
    else if ((c[0] == n) && (c[4] == n) && (c[8] == n))
        return 1;
    else if ((c[2] == n) && (c[4] == n) && (c[6] == n))
        return 1;
    else
        return 0;

}

} `


0
投票

下面的解决方案使用递归生成所有可能的组合

排除了不可能的组合,返回了888个组合

下面是一个工作代码TIC TAC TOE游戏的可能获胜组合

const players = ['X', 'O'];
let gameBoard = Array.from({ length: 9 });

const winningCombination = [
  [ 0, 1, 2 ],
  [ 3, 4, 5 ],
  [ 6, 7, 8 ],
  [ 0, 3, 6 ],
  [ 1, 4, 7 ],
  [ 2, 5, 8 ],
  [ 0, 4, 8 ],
  [ 2, 4, 6 ],
];

const isWinningCombination = (board)=> {
  if((Math.abs(board.filter(a => a === players[0]).length - 
  board.filter(a => a === players[1]).length)) > 1) {
    return false
  }
  let winningComb = 0;
  players.forEach( player => {
    winningCombination.forEach( combinations => {
      if (combinations.every(combination => board[combination] === player )) {
        winningComb++;
      }
    });
  });
  return winningComb === 1;
}

const getCombinations = (board) => {
  let currentBoard = [...board];
  const firstEmptySquare = board.indexOf(undefined)
  if (firstEmptySquare === -1) {
    return isWinningCombination(board) ? [board] : [];
  } else {
    return [...players, ''].reduce((prev, next) => {
      currentBoard[firstEmptySquare] = next;
      if(next !== '' && board.filter(a => a === next).length > (gameBoard.length / players.length)) {
        return [...prev]
      }
      return [board, ...prev, ...getCombinations(currentBoard)]
    }, [])

  }
}

const startApp = () => {
  let combination = getCombinations(gameBoard).filter(board => 
      board.every(item => !(item === undefined)) && isWinningCombination(board)
    )
  printCombination(combination)
}

const printCombination = (combination)=> {
  const ulElement = document.querySelector('.combinations');
  combination.forEach(comb => {
    let node = document.createElement("li");
    let nodePre = document.createElement("pre");
    let textnode = document.createTextNode(JSON.stringify(comb));
    nodePre.appendChild(textnode);
    node.appendChild(nodePre); 
    ulElement.appendChild(node);
  })
}
startApp();

0
投票

这会发现井字游戏 (255,168) 的所有可能组合——使用递归用 JavaScript 编写。它没有优化,但可以满足您的需求。

const [EMPTY, O, X] = [0, 4, 1]
let count = 0 

let coordinate = [
    EMPTY, EMPTY, EMPTY, 
    EMPTY, EMPTY, EMPTY, 
    EMPTY, EMPTY, EMPTY
]

function reducer(arr, sumOne, sumTwo = null) {
    let func = arr.reduce((sum, a) => sum + a, 0)
    if((func === sumOne) || (func === sumTwo)) return true
}

function checkResult() {
    let [a1, a2, a3, b1, b2, b3, c1, c2, c3] = coordinate
    if(reducer([a1,a2,a3], 3, 12)) return true
    if(reducer([a1,b2,c3], 3, 12)) return true
    if(reducer([b1,b2,b3], 3, 12)) return true
    if(reducer([c1,c2,c3], 3, 12)) return true
    if(reducer([a3,b2,c1], 3, 12)) return true
    if(reducer([a1,b1,c1], 3, 12)) return true
    if(reducer([a2,b2,c2], 3, 12)) return true
    if(reducer([a3,b3,c3], 3, 12)) return true
    if(reducer([a1,a2,a3,b1,b2,b3,c1,c2,c3], 21)) return true
    return false
}

function nextPiece() {
    let [countX, countO] = [0, 0]
    for(let i = 0; i < coordinate.length; i++) {
        if(coordinate[i] === X) countX++
        if(coordinate[i] === O) countO++
    }
    return countX === countO ? X : O
}

function countGames() {
    if (checkResult()) {
        count++
    }else {
        for (let i = 0; i < 9; i++) {
            if (coordinate[i] === EMPTY) {
                coordinate[i] = nextPiece()
                countGames()
                coordinate[i] = EMPTY
            }
        }
    }
}

countGames()
console.log(count)

如果您想输出各种获胜条件,我会分离出 checkResult 返回值。


0
投票

这个问题的大部分答案都很慢所以这里有一个更快的方法


def won(c, n):
  if c[0] == n and c[1] == n and c[2] == n: return 1
  if c[3] == n and c[4] == n and c[5] == n: return 1
  if c[6] == n and c[7] == n and c[8] == n: return 1

  if c[0] == n and c[3] == n and c[6] == n: return 1
  if c[1] == n and c[4] == n and c[7] == n: return 1
  if c[2] == n and c[5] == n and c[8] == n: return 1

  if c[0] == n and c[4] == n and c[8] == n: return 1
  if c[2] == n and c[4] == n and c[6] == n: return 1

  return 0

for i in range(3**9):
    grid = [i // c for i in range(9)]
    if won(grid, 1) + won(grid, 2) == 1:
       print(grid)


-1
投票

可以用蛮力解决,但请记住当 player1 获胜时 player2 等极端情况不能移动,反之亦然。还要记住 player1 和 player 的移动之间的差异不能大于 1 且小于 0.

我已经编写了验证提供的组合是否有效的代码,可能很快会发布在 github 上。

© www.soinside.com 2019 - 2024. All rights reserved.