Simulate 2048 and pack board into uint64
Company: Citadel
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates proficiency in implementing deterministic game-state logic and compact binary representations, specifically array manipulation and merge semantics for 2048 slide operations plus bit-level packing/unpacking of 4-bit exponent fields.
Part 1: Simulate a 2048 Move
Constraints
- board is always 4x4
- Each cell is 0 or a power of two
- direction is one of 'LEFT', 'RIGHT', 'UP', 'DOWN'
- You must follow the rule that each tile merges at most once per move
Examples
Input: ([[2, 0, 2, 4], [0, 4, 4, 8], [2, 2, 2, 2], [0, 0, 0, 2]], 'LEFT')
Expected Output: [[4, 4, 0, 0], [8, 8, 0, 0], [4, 4, 0, 0], [2, 0, 0, 0]]
Explanation: Each row is compressed to the left, with equal adjacent tiles merging once.
Input: ([[2, 2, 2, 0], [4, 0, 4, 4], [2, 2, 4, 4], [0, 0, 0, 0]], 'RIGHT')
Expected Output: [[0, 0, 2, 4], [0, 0, 4, 8], [0, 0, 4, 8], [0, 0, 0, 0]]
Explanation: Merges happen from the side of movement, and no tile merges twice.
Input: ([[2, 0, 2, 2], [2, 4, 0, 2], [4, 4, 2, 0], [4, 0, 2, 2]], 'UP')
Expected Output: [[4, 8, 4, 4], [8, 0, 2, 2], [0, 0, 0, 0], [0, 0, 0, 0]]
Explanation: Process each column as if it were a row moving left, then rebuild the board.
Input: ([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], 'DOWN')
Expected Output: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
Explanation: Edge case: an empty board stays unchanged.
Solution
def solution(board, direction):
def merge_line(line):
nums = [x for x in line if x != 0]
merged = []
i = 0
while i < len(nums):
if i + 1 < len(nums) and nums[i] == nums[i + 1]:
merged.append(nums[i] * 2)
i += 2
else:
merged.append(nums[i])
i += 1
merged.extend([0] * (4 - len(merged)))
return merged
if direction == 'LEFT':
return [merge_line(row) for row in board]
if direction == 'RIGHT':
return [list(reversed(merge_line(list(reversed(row))))) for row in board]
if direction == 'UP':
cols = list(zip(*board))
merged_cols = [merge_line(list(col)) for col in cols]
return [list(row) for row in zip(*merged_cols)]
if direction == 'DOWN':
cols = list(zip(*board))
merged_cols = [list(reversed(merge_line(list(reversed(col))))) for col in cols]
return [list(row) for row in zip(*merged_cols)]
raise ValueError('Invalid direction')Time complexity: O(1). Space complexity: O(1).
Hints
- Write a helper that processes a single line moving left: remove zeros, merge equal neighbors once, then pad with zeros.
- You can reuse the same helper for RIGHT, UP, and DOWN by reversing rows or working column-by-column.
Part 2: Encode and Decode a 4x4 2048 Board into 64 Bits
Constraints
- board is always 4x4
- Each cell is 0 or a power of two
- No cell exceeds 32768, so its exponent fits in 4 bits
- Use row-major order with cell index k = 4 * row + col
Examples
Input: ([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
Expected Output: (0x0, [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
Explanation: Edge case: all empty cells produce the integer 0.
Input: ([[2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
Expected Output: (0x1, [[2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
Explanation: The top-left cell has exponent 1, stored in the least-significant 4 bits.
Input: ([[2, 4, 8, 16], [0, 0, 0, 0], [32, 64, 128, 256], [512, 1024, 2048, 4096]])
Expected Output: (0xCBA9876500004321, [[2, 4, 8, 16], [0, 0, 0, 0], [32, 64, 128, 256], [512, 1024, 2048, 4096]])
Explanation: The exponents in row-major order are 1,2,3,4,0,0,0,0,5,6,7,8,9,10,11,12.
Input: ([[32768, 32768, 32768, 32768], [32768, 32768, 32768, 32768], [32768, 32768, 32768, 32768], [32768, 32768, 32768, 32768]])
Expected Output: (0xFFFFFFFFFFFFFFFF, [[32768, 32768, 32768, 32768], [32768, 32768, 32768, 32768], [32768, 32768, 32768, 32768], [32768, 32768, 32768, 32768]])
Explanation: Boundary case: exponent 15 in every cell fills all 16 nibbles with F.
Solution
def solution(board):
encoded = 0
for r in range(4):
for c in range(4):
value = board[r][c]
exponent = 0 if value == 0 else value.bit_length() - 1
index = 4 * r + c
encoded |= exponent << (4 * index)
decoded = [[0] * 4 for _ in range(4)]
for index in range(16):
exponent = (encoded >> (4 * index)) & 0xF
decoded[index // 4][index % 4] = 0 if exponent == 0 else 1 << exponent
return encoded, decodedTime complexity: O(1). Space complexity: O(1).
Hints
- For a non-zero power of two, the exponent can be found with bit_length() - 1.
- To pack cell k, shift its 4-bit exponent left by 4 * k. To unpack, mask with 0xF after shifting right.