Candy Crush

Candy Crush

·

4 min read

In this article, let's see how to solve the leetcode candy-crush problem.

Candy crush is a popular game. The rules are very simple, just crush candies with the same type(or color).

Look at the image below, which I took from the leetcode website to show the basic idea of the rules.

The solution is not that hard to figure out. We need to do 3 things:

  • to identify the candies which should be crushed

  • to move candies down if there is empy space below

  • to do it recursively until there is nothing to crush

Well, let's get started.

Identifing candies to crush is a bit tricky. At first, I tried to use a BFS approach to get candies. But it was not working because below invalid cases cannot be identified.

xx
 x
 xx

As you can see, for this problem, only candies with number above 3 in a row or column should be considered valid. BFS is not enough, we need to extra check.

The simple way is to just use a sliding window to check if the candies are the same type. But we have another case to consider.

  x
  x
xxx

In this case, if we crush the column first, then we will leave the below row only 2 items left. So we need to figure out a way to tag the candies first, and after all tagging, then clear them together.

We can use extra memory to tag which candies should be crushed later. But an useful tip is that we can make the value negative as a flag and also we can get the positive value easily we know the original value.

Yes. This is all for the first part. Let's see it in code.

for (let i = 0; i < m; i++) {
  for (let j = 0; j < n - 2; j++) {
    if (board[i][j] === 0) continue;
    if (
      Math.abs(board[i][j]) === Math.abs(board[i][j + 1]) &&
      Math.abs(board[i][j]) === Math.abs(board[i][j + 2])
    ) {
      board[i][j] =
        board[i][j + 1] =
        board[i][j + 2] =
          -Math.abs(board[i][j]);
    }
  }
}
for (let j = 0; j < n; j++) {
  for (let i = 0; i < m - 2; i++) {
    if (board[i][j] === 0) continue;
    if (
      Math.abs(board[i][j]) === Math.abs(board[i + 1][j]) &&
      Math.abs(board[i][j]) === Math.abs(board[i + 2][j])
    ) {
      board[i][j] =
        board[i + 1][j] =
        board[i + 2][j] =
          -Math.abs(board[i][j]);
    }
  }
}

The second issue is to move candies down. The navie way is to iterate candies from bottom to top, if we encounter empty space, move above candies down.

This way is working but not efficient. Time complexity can be O(N^2). We could use a 2 pointers way to handle it. We define a read pointer and write pointer. Read pointer will read column candies from bottom to top. If the space is not empty, we can write to the write pointer. In this way, the time complexity can be reduced to O(N).

for (let j = 0; j < n; j++) {
  let writeIndex = m - 1;
  for (let readIndex = m - 1; readIndex >= 0; readIndex--) {
    if (board[readIndex][j] < 0) continue;
    board[writeIndex--][j] = board[readIndex][j];
  }
  while (writeIndex >= 0) {
    board[writeIndex--][j] = 0;
  }
}

OK. The last part is the recursive part. We can just use a boolean value to indicate if we need to check the board again.

That's all. The whole code is in below.

function candyCrush(board: number[][]): number[][] {
  const m = board.length;
  const n = board[0].length;
  let done = true;

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n - 2; j++) {
      if (board[i][j] === 0) continue;
      if (
        Math.abs(board[i][j]) === Math.abs(board[i][j + 1]) &&
        Math.abs(board[i][j]) === Math.abs(board[i][j + 2])
      ) {
        board[i][j] =
          board[i][j + 1] =
          board[i][j + 2] =
            -Math.abs(board[i][j]);
        done = false;
      }
    }
  }
  for (let j = 0; j < n; j++) {
    for (let i = 0; i < m - 2; i++) {
      if (board[i][j] === 0) continue;
      if (
        Math.abs(board[i][j]) === Math.abs(board[i + 1][j]) &&
        Math.abs(board[i][j]) === Math.abs(board[i + 2][j])
      ) {
        board[i][j] =
          board[i + 1][j] =
          board[i + 2][j] =
            -Math.abs(board[i][j]);
        done = false;
      }
    }
  }

  for (let j = 0; j < n; j++) {
    let writeIndex = m - 1;
    for (let readIndex = m - 1; readIndex >= 0; readIndex--) {
      if (board[readIndex][j] < 0) continue;
      board[writeIndex--][j] = board[readIndex][j];
    }
    while (writeIndex >= 0) {
      board[writeIndex--][j] = 0;
    }
  }

  return done ? board : candyCrush(board);
}