【问题标题】:All Possible Tic Tac Toe Winning Combinations所有可能的井字游戏获胜组合
【发布时间】:2015-04-27 01:10:31
【问题描述】:

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

【问题讨论】:

  • paxdiablo 的回答有效;你也可以从“另一边”接近它:从一个空白棋盘开始,玩所有可能的游戏,跟踪所达到的最终获胜位置。这将比 paxdiablo 的答案更有效,但对于比井字游戏更复杂的游戏可能会变得更容易。

标签: algorithm combinations graph-algorithm


【解决方案1】:

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

在网格中放置xo<blank> 只有 39 或 19,683 种可能的组合,而且并非所有这些都有效。

首先,一个有效的游戏位置是xo 计数之间的差异不超过一个,因为它们必须交替移动。

另外,不可能有两边连续三个的状态,所以也可以打折。如果两者都连续三个,那么他们中的一个会在上一步中获胜。

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

XXX
OOO
XXX

无法实现,而:

XXX
OOX
OOX

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

所以我会简单地使用蛮力,并且对于计数之间差异为零或一的每个位置,检查双方的八种获胜可能性。假设其中只有一个获胜,这是一场合法的获胜游戏。


下面是 Python 中的概念证明,但首先是在将输出发送到 /dev/null 的进程上运行时 time 的输出,以显示它有多快:

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

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

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

【讨论】:

  • @Pham,可能,但目的是什么?我们不需要在这里节省时间和空间,使用位意味着打包和解包,这可能会使代码复杂。除非我误解了,在这种情况下,我会澄清一下。
  • 我明白你的意思 :) 完全忘记了空格选项,所以我会收回我的评论 :)
  • 一招之差不应该是双向的。对手获胜后,失败者无法移动。
  • @JanneKarila,好点子,我会将其添加到答案中,但我不会费心更改代码。
  • 感谢@paxdiablo 的详细回答!对于我们非 php 开发人员,您是否介意澄清一下代码发生了什么,特别是在最后一个 for 循环中?
【解决方案2】:

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

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

并递归填写所有合法组合(从插入 2 个o 开始,然后为每个o 添加一个x;避免o 获胜位置):

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

【讨论】:

    【解决方案3】:

    这是一个 java 等效代码示例

    打包testit;

    公共课井字游戏{

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

    } `

    【讨论】:

      【解决方案4】:

      今天我接受了苹果的采访,我也有同样的问题。那一刻我无法好好思考。后来,在去开会之前,我在 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]
      */
      

      【讨论】:

        【解决方案5】:

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

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

        下面是工作代码Possible winning combinations of the TIC TAC TOE game

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

        【讨论】:

          【解决方案6】:

          可以通过蛮力解决,但请记住,当玩家 1 获胜时玩家 2 无法移动,反之亦然。还要记住 player1 和 player 的移动之间的差异不能大于 1 和小于 0。

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

          【讨论】:

          • 感谢您的贡献,但您写的所有内容都已包含在 @paxdiablo 的答案中。
          • 如果 player1 是赢家,那么他们甚至不能拥有相等的单元格,这是未提及的极端情况之一。也不要认为这需要投反对票,否则新来者很难增加贡献。
          猜你喜欢
          • 1970-01-01
          • 2022-07-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多