Google Interview Question for Software Engineer / Developers






Comment hidden because of low score. Click to expand.
0
of 0 vote

let's say you take a one or two dimensional array for the positions; wouldn't all the empty positions be valid moves after that? I think this is about a logical minimax implementation. (logical moves rather than valid?)

- Anonymous November 30, 2010 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 votes

"game" means a complete game. So print all possible moves that make a valid game - X wins, O wins, or draw. How many different games are there.

- Anonymous December 01, 2010 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 vote

It looks to me that we can solve this question with a tree where every node can have up to 9 branches. Lets assume O starts first.

- The root node represents an empty board.
- The second level will have 9 edges and nodes since there are 9 places that we can use for O (or X, whichever starts first.) Each node will have (row,column) indices.
- X plays the next time. Since one of the board cells is occupied, we can now have 8 places to move.
...
This goes until we have only one node.

Once we created the tree, we list the paths by using DFS. There will be 9! different paths. Since 9 is a constant, The complexity is constant too. But if we have different version of the game with a bigger board than complexity will be O(N!) which is O(2^N)

Please correct me if I am wrong or suggest a better algorithm.

- Aytekin December 03, 2010 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 votes

This solution might be using too much space compared to a permutation function that might use a small array. Let me know your comments.

- Aytekin December 03, 2010 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

Ok. Let's get back to my tree solution. It lists *ALL* possible moves. But if one of them wins, we should stop there. Some cells will be empty since the game is over. I guess what we can do is to list all possible winning in an array, and compare the paths if we reach any of the winning configurations.

There are just 9 winning cases:
- 3 rows (use indices like
- 3 columns
- 2 diagonals.

Ex:{{(0,0),(0,1),(0,2)}, {(1,0), (1,1)....}}

- Aytekin December 04, 2010 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

look at my programmatic solution blow. it's almost the same you offered.

- blackorangebox December 04, 2010 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

>> we list the paths by using DFS

What is DFS?

- blackorangebox December 04, 2010 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

Depth First Search

- Anonymous December 06, 2010 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 vote

I created a programmatic solution for the case. It uses almost full tree of field combination and don't seem to sweet cause is't bruteforce.

package test;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

public class TicTacToe {
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		System.out.println("Game processing started.");

		Level init = Level.getInitialLevel(true);
		int c = init.getGamesCount();

		System.out.println("\n---------------------------------------");
		System.out.println("Games count for X-starter player = " + c);
		System.out.println("Games count for both players = " + c * 2);

		System.out.println("---------------------------------------");
		System.out.println("Total field object created: "
				+ Field.totalFieldsCount);
		System.out.println("Total level object created: "
				+ Level.totalLevelsCount);
		System.out.println("Note: 9! is " + fact(9));
		System.out.println("Total game processing time: "
				+ (System.currentTimeMillis() - start) / 1000.0 + " s.");

	}

	static class Field {
		static long totalFieldsCount = 0;

		static int MAX_FIELD_CELLS = 9;

		BitSet cells = new BitSet(MAX_FIELD_CELLS);
		// true means cell is empty
		BitSet emptyCells = new BitSet(MAX_FIELD_CELLS);

		public Field() {
			emptyCells.flip(0, MAX_FIELD_CELLS);
		}

		public Field(Field prevLevField) {
			totalFieldsCount++;
			cells = (BitSet) prevLevField.cells.clone();
			emptyCells = (BitSet) prevLevField.emptyCells.clone();
		}

		public String getCellContentsForToString(int idx) {
			if (emptyCells.get(idx)) {
				return "_ ";
			} else {
				return cells.get(idx) ? "X " : "O ";
			}
		}

		public String toString() {
			StringBuilder buff = new StringBuilder();
			buff.append(getCellContentsForToString(0));
			buff.append(getCellContentsForToString(1));
			buff.append(getCellContentsForToString(2));
			buff.append("\n");
			buff.append(getCellContentsForToString(3));
			buff.append(getCellContentsForToString(4));
			buff.append(getCellContentsForToString(5));
			buff.append("\n");
			buff.append(getCellContentsForToString(6));
			buff.append(getCellContentsForToString(7));
			buff.append(getCellContentsForToString(8));
			buff.append("\n");

			return buff.toString();

		}

		boolean putVal(int idx, boolean player) {
			if (!emptyCells.get(idx)) {
				throw new IllegalArgumentException(String.format(
						"Cell with idx %s is not empty.", idx));
			}
			emptyCells.set(idx, false);
			cells.set(idx, player);

			return checkIsGameOver();
		}

		boolean makeMove(BitSet foreignEmptySections, boolean player) {
			int clearIdx = foreignEmptySections.nextSetBit(0);
			foreignEmptySections.set(clearIdx, false);
			return putVal(clearIdx, player);
		}

		private boolean checkIsGameOver() {
			if (getEmptySectionsCount() == 0) {
				return true;
			}
			if (checkLineIsWinLine(0, 1, 2) || checkLineIsWinLine(3, 4, 5)
					|| checkLineIsWinLine(6, 7, 8)
					|| checkLineIsWinLine(0, 3, 6)
					|| checkLineIsWinLine(1, 4, 7)
					|| checkLineIsWinLine(2, 5, 8)
					|| checkLineIsWinLine(0, 4, 8)
					|| checkLineIsWinLine(2, 4, 6)) {
				System.out.println(toString());
				return true;
			}
			return false;
		}

		boolean checkLineIsWinLine(int i1, int i2, int i3) {
			if (!emptyCells.get(i1) && !emptyCells.get(i2)
					&& !emptyCells.get(i3)) {
				if (cells.get(i1) && cells.get(i2) && cells.get(i3)) {
					return true;
				}
				if (!cells.get(i1) && !cells.get(i2) && !cells.get(i3)) {
					return true;
				}
			}
			return false;
		}

		public int getEmptySectionsCount() {
			return emptyCells.cardinality();
		}

		public BitSet getEmptyCells() {
			return (BitSet) emptyCells.clone();
		}
	}

	static class Level {
		int levelDepth = 0;

		List<Field> fields = new ArrayList<Field>(9);
		boolean currentPlayer;

		Field prevLevField;

		public static Level getInitialLevel(boolean player) {
			Field prevLevField = new Field();
			return new Level(prevLevField, player, 0);
		}

		public int getGamesCount() {
			return makeMove();
		}

		public static int totalLevelsCount = 0;

		public Level(Field prevLevField, boolean currentPlayer, int levelDepth) {
			totalLevelsCount++;
			this.levelDepth = levelDepth;
			this.currentPlayer = currentPlayer;
			this.prevLevField = prevLevField;
			int emptyCells = prevLevField.getEmptySectionsCount();

			for (int i = 0; i < emptyCells; i++) {
				fields.add(new Field(prevLevField));
			}
		}

		int makeMove() {
			// System.out.println("level depth = " + levelDepth);
			BitSet prevLevFieldEmptyCells = prevLevField.getEmptyCells();
			int gamesCounter = 0;
			for (Field field : fields) {
				// System.out.println(field);
				boolean gameOver = field.makeMove(prevLevFieldEmptyCells,
						currentPlayer);
				// System.out.println(field);
				if (gameOver) {
					// System.out.print("0");
					gamesCounter++;
					continue;
				}
				Level nextLevel = new Level(field, !currentPlayer,
						levelDepth + 1);
				gamesCounter += nextLevel.makeMove();
			}
			return gamesCounter;
		}
	}

	private static int fact(int in) {
		if (in == 0) {
			return 1;
		}
		return in * fact(--in);
	}

}

output:

---------------------------------------
Games count for X-starter player = 255168
Games count for both players = 510336
---------------------------------------
Total field object created: 549945
Total level object created: 294778
Note: 9! is 362880
Total game processing time: 7.512 s.

- blackorangebox December 04, 2010 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

I designed recursive algo up to either win case or board is full. Plus I counted the interesting for me statistics. It turned out that starter has 2 times more winning situations to choose from. Processing time: 0.3 sec.

unsigned int g_ValidMoves;
unsigned int g_FirstWon;
unsigned int g_SecondWon;

enum ETicTacCellValue
{
eEmpty = 0,
eTic,
eTac
};

class CTicTacBoard
{
public:
CTicTacBoard()
{
m_bGameIsOver = false;
m_winner = eEmpty;

for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
m_Board[i][j] = eEmpty;
}

CTicTacBoard(CTicTacBoard& board)
{
m_bGameIsOver = board.m_bGameIsOver;
m_winner = board.m_winner;

for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
m_Board[i][j] = board.m_Board[i][j];
}

bool IsValidMove(unsigned int row, unsigned int col)
{
return (!m_bGameIsOver && row < 3 && col < 3 && m_Board[row][col] == eEmpty);
}

bool SetMove(unsigned int row, unsigned int col, ETicTacCellValue value)
{
bool bResult = false;

if (IsValidMove(row, col) && value != eEmpty && m_Board[row][col] == eEmpty)
{
m_Board[row][col] = value;
CheckIfGameIsOver();
bResult = true;
}

return bResult;
}

bool IsGameOver(ETicTacCellValue& winner)
{
winner = m_winner;
return m_bGameIsOver;
}

private:
void CheckIfGameIsOver()
{
if ((m_Board[0][0] == eTic && m_Board[0][1] == eTic && m_Board[0][2] == eTic) ||
(m_Board[1][0] == eTic && m_Board[1][1] == eTic && m_Board[1][2] == eTic) ||
(m_Board[2][0] == eTic && m_Board[2][1] == eTic && m_Board[2][2] == eTic) ||
(m_Board[0][0] == eTic && m_Board[1][0] == eTic && m_Board[2][0] == eTic) ||
(m_Board[0][1] == eTic && m_Board[1][1] == eTic && m_Board[2][1] == eTic) ||
(m_Board[0][2] == eTic && m_Board[1][2] == eTic && m_Board[2][2] == eTic) ||
(m_Board[0][0] == eTic && m_Board[1][1] == eTic && m_Board[2][2] == eTic) ||
(m_Board[2][0] == eTic && m_Board[1][1] == eTic && m_Board[0][2] == eTic))
{
m_winner = eTic;
m_bGameIsOver = true;
}
else if ((m_Board[0][0] == eTac && m_Board[0][1] == eTac && m_Board[0][2] == eTac) ||
(m_Board[1][0] == eTac && m_Board[1][1] == eTac && m_Board[1][2] == eTac) ||
(m_Board[2][0] == eTac && m_Board[2][1] == eTac && m_Board[2][2] == eTac) ||
(m_Board[0][0] == eTac && m_Board[1][0] == eTac && m_Board[2][0] == eTac) ||
(m_Board[0][1] == eTac && m_Board[1][1] == eTac && m_Board[2][1] == eTac) ||
(m_Board[0][2] == eTac && m_Board[1][2] == eTac && m_Board[2][2] == eTac) ||
(m_Board[0][0] == eTac && m_Board[1][1] == eTac && m_Board[2][2] == eTac) ||
(m_Board[2][0] == eTac && m_Board[1][1] == eTac && m_Board[0][2] == eTac))
{
m_winner = eTac;
m_bGameIsOver = true;
}
}

private:
ETicTacCellValue m_Board[3][3];
bool m_bGameIsOver;
ETicTacCellValue m_winner;
};

void MakeNextMove(CTicTacBoard* pBoard, ETicTacCellValue currPlayer)
{
ETicTacCellValue winner;
ETicTacCellValue nextPlayer;

if (pBoard->IsGameOver(winner))
{
if (winner == eTic)
g_FirstWon++;
else
g_SecondWon++;
}
else
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (pBoard->IsValidMove(i, j))
{
g_ValidMoves++;
CTicTacBoard newBoard(*pBoard);
if (currPlayer == eTic)
nextPlayer = eTac;
else if (currPlayer == eTac)
nextPlayer = eTic;
else
{
printf("\nUnexpected error! Empty player passed in MakeNextMove.\n");
return;
}

newBoard.SetMove(i, j, currPlayer);
MakeNextMove(&newBoard, nextPlayer);
}
}
}
}

void TicTacGame()
{
CTicTacBoard Boards[3][3];

g_ValidMoves = 0;
g_FirstWon = 0;
g_SecondWon = 0;

// Lets start allways from eTic
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
Boards[i][j].SetMove(i, j, eTic);
MakeNextMove(&(Boards[i][j]), eTac);
}

printf("\nNumber of all Valid moves = %d\t(100%%)\n", g_ValidMoves);
printf("\nNumber of Non win cases = %d\t(%d%%) \n", g_ValidMoves - g_FirstWon - g_SecondWon,
100 * (g_ValidMoves - g_FirstWon - g_SecondWon) / g_ValidMoves);
printf("\nNumber of First player won = %d\t(%d%%)\n", g_FirstWon, 100 * g_FirstWon / g_ValidMoves);
printf("\nNumber of Second player won = %d\t(%d%%)\n", g_SecondWon, 100 * g_SecondWon / g_ValidMoves);
}

Number of all Valid moves = 549936 (100%)

Number of Non win cases = 340848 (61%)

Number of First player won = 131184 (23%)

Number of Second player won = 77904 (14%)

- Anonymous December 11, 2010 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

I think a way to make the algorithm faster but still using DFS is to limit the first move to just one out three: (0,0), (1,0), (1,1). Analogously, the second move can be limited to the above (or below) diagonal positions. All the other possible initial moves are generated from these by simple rotation/mirror. This would save in both space and time complexity since we are cutting the graph at the root. The total cost would be then 3*5*(7!)=75600, with the caveat to add rotated/mirrored moves when printing the total set of available moves.

- Giovanni December 13, 2010 | Flag Reply


Add a Comment
Name:

Writing Code? Surround your code with {{{ and }}} to preserve whitespace.

Books

is a comprehensive book on getting a job at a top tech company, while focuses on dev interviews and does this for PMs.

Learn More

Videos

CareerCup's interview videos give you a real-life look at technical interviews. In these unscripted videos, watch how other candidates handle tough questions and how the interviewer thinks about their performance.

Learn More

Resume Review

Most engineers make critical mistakes on their resumes -- we can fix your resume with our custom resume review service. And, we use fellow engineers as our resume reviewers, so you can be sure that we "get" what you're saying.

Learn More

Mock Interviews

Our Mock Interviews will be conducted "in character" just like a real interview, and can focus on whatever topics you want. All our interviewers have worked for Microsoft, Google or Amazon, you know you'll get a true-to-life experience.

Learn More