You are given a 2D integer array board
representing the state of a Candy Crush game. The goal is to simulate the candy crushing process until no more moves can be made. A move consists of identifying and removing all contiguous horizontal or vertical lines of three or more candies of the same type. Once the candies are crushed (replaced with 0), the candies above them fall down to fill the empty spaces. The process repeats until there are no more crushable candies. Return the final state of the board.
Detailed Rules:
Example:
Input:
board = [
[110,5,112,113,114],
[210,5,212,213,214],
[310,5,312,313,314],
[410,5,412,413,414],
[510,5,512,513,514],
[610,5,612,613,614],
[710,5,712,713,714]
]
Output:
[
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[110,5,112,113,114],
[210,5,212,213,214],
[310,5,312,313,314],
[410,5,412,413,414]
]
Explanation:
The only crushable sequence is a vertical line of three 5s. After crushing and applying gravity, the board becomes the output.
Constraints:
1 <= board.length <= 50
1 <= board[i].length <= 50
1 <= board[i][j] <= 2000
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force way to solve Candy Crush is to repeatedly scan the board for any groups of three or more candies in a row or column. When we find a group, we crush it and let the remaining candies fall down to fill the gaps. We keep doing this until there are no more groups to crush.
Here's how the algorithm would work step-by-step:
def candy_crush_brute_force(board):
rows = len(board)
columns = len(board[0])
def crush_board(board):
rows_to_crush = set()
columns_to_crush = set()
# Find horizontal matches
for row_index in range(rows):
for column_index in range(columns - 2):
if board[row_index][column_index] != 0 and \
board[row_index][column_index] == board[row_index][column_index + 1] and \
board[row_index][column_index] == board[row_index][column_index + 2]:
rows_to_crush.add((row_index, column_index))
rows_to_crush.add((row_index, column_index + 1))
rows_to_crush.add((row_index, column_index + 2))
# Find vertical matches
for row_index in range(rows - 2):
for column_index in range(columns):
if board[row_index][column_index] != 0 and \
board[row_index][column_index] == board[row_index + 1][column_index] and \
board[row_index][column_index] == board[row_index + 2][column_index]:
columns_to_crush.add((row_index, column_index))
columns_to_crush.add((row_index + 1, column_index))
columns_to_crush.add((row_index + 2, column_index))
# Crush candies
if not rows_to_crush and not columns_to_crush:
return False
for row_index, column_index in rows_to_crush:
board[row_index][column_index] = 0
for row_index, column_index in columns_to_crush:
board[row_index][column_index] = 0
# Apply gravity
for column_index in range(columns):
bottom = rows - 1
for row_index in range(rows - 1, -1, -1):
if board[row_index][column_index] != 0:
board[bottom][column_index] = board[row_index][column_index]
bottom -= 1
# Fill top with zeros
for row_index in range(bottom, -1, -1):
board[row_index][column_index] = 0
return True
# Keep crushing until no more crushes are possible
while crush_board(board):
continue
return board
The most efficient way to 'Candy Crush' is to repeatedly identify and remove matching groups until no more matches exist. This involves scanning the board and eliminating all matches in each pass before moving to the next iteration.
Here's how the algorithm would work step-by-step:
def candy_crush(board):
rows = len(board)
columns = len(board[0])
def mark_horizontal_matches():
for row_index in range(rows):
for column_index in range(columns - 2):
if board[row_index][column_index] != 0 and \
board[row_index][column_index] == board[row_index][column_index + 1] and \
board[row_index][column_index] == board[row_index][column_index + 2]:
# Mark horizontal matches as negative
board[row_index][column_index] = -abs(board[row_index][column_index])
board[row_index][column_index + 1] = -abs(board[row_index][column_index + 1])
board[row_index][column_index + 2] = -abs(board[row_index][column_index + 2])
def mark_vertical_matches():
for row_index in range(rows - 2):
for column_index in range(columns):
if board[row_index][column_index] != 0 and \
board[row_index][column_index] == board[row_index + 1][column_index] and \
board[row_index][column_index] == board[row_index + 2][column_index]:
# Mark vertical matches as negative
board[row_index][column_index] = -abs(board[row_index][column_index])
board[row_index + 1][column_index] = -abs(board[row_index + 1][column_index])
board[row_index + 2][column_index] = -abs(board[row_index + 2][column_index])
def drop_candies():
for column_index in range(columns):
bottom = rows - 1
for row_index in range(rows - 1, -1, -1):
if board[row_index][column_index] > 0:
board[bottom][column_index] = board[row_index][column_index]
bottom -= 1
# Fill the remaining top cells with 0
for row_index in range(bottom, -1, -1):
board[row_index][column_index] = 0
while True:
mark_horizontal_matches()
mark_vertical_matches()
has_match = False
for row_index in range(rows):
for column_index in range(columns):
if board[row_index][column_index] < 0:
has_match = True
break
if has_match:
break
if not has_match:
break
# Remove matches by setting them to 0
for row_index in range(rows):
for column_index in range(columns):
if board[row_index][column_index] < 0:
board[row_index][column_index] = 0
# Drop candies to fill empty spaces
drop_candies()
return board
Case | How to Handle |
---|---|
Null or empty board | Return the null or empty board immediately, as there's nothing to crush. |
Board with dimensions 1xN or Nx1 | Check for consecutive elements only in the direction that has length greater than or equal to 3. |
Board with only one type of candy | Continuously crush until the board stabilizes (no more crushes). |
Large board dimensions (performance) | Optimize crushing logic to avoid redundant checks and consider iterative solutions for better performance than recursive ones. |
No crushes possible (stable board) | The algorithm should terminate gracefully, returning the original board or indicating stability. |
Integer overflow during crush calculation (if using counts) | Use appropriate data types (e.g., long) or avoid direct multiplication of large numbers representing count. |
Multiple crushes possible simultaneously (overlapping) | Mark all candies to be crushed in a single pass before actually crushing them, to avoid side effects. |
Board filled with zeros | The crushing algorithm should treat zeros as normal candy and check for consecutive occurrences. |