1、题目描述
2、解题思路
本题主要有两个步骤:找出需要粉碎的糖果所在的格子、粉碎糖果。
定义一个变量 todo 作为是否需要粉碎的标记。
一行一行遍历:当遇到三个及以上连续相同的值,则标记为原值的负数形式,并 todo = true;
一列一列遍历,当遇到三个及以上连续相同的值,则标记为原值的负数形式,并 todo = true;
粉碎糖果的逻辑是:
1、从左到右遍历每一列,定义一个值 wr 初始值为最后一行的索引 rows - 1;
2、wr 行的含义是 “接收上面掉下来的糖果” 的所在行,因此先从最低行往上遍历,直到遇到第一个待粉碎的糖果,wr 的初始值就是这一行;
3、wr 确定后,r 继续往上走,直到遇到不需要粉碎的糖果,就把它掉下去,落在 wr 所在的格子里。然后 wr 往上走一格,r 继续往上找需要掉下来的糖果。
4、当 r 走到最上面了,此时 wr 还在中间某个为止,说明掉落的糖果已经全部处理完毕,wr 往上全都是粉碎后的空白区。
我们粉碎结束后,其实不确定新的状态是否有新的要粉碎的糖果,因此,只要本次进行过粉碎,就得再执行一次上面两个流程,如果再执行一次却从头到尾 todo 都是 false,则说明算法结束,已经到了稳定的状态。
3、解题代码
class Solution {
public int[][] candyCrush(int[][] board) {
int rows = board.length, cols = board[0].length;
boolean todo = false; // 是否存在要粉碎的糖果
// 一行一行扫描
for (int r = 0; r < rows; ++r) {
for (int c = 0; c + 2 < cols; ++c) {
// 取出这个点的绝对值(可能被取反了,所以要绝对值)
int v = Math.abs(board[r][c]);
// 如果连续三个格子是相同的糖果
if (v != 0 && v == Math.abs(board[r][c + 1]) && v == Math.abs(board[r][c + 2])) {
// 把这三个连续格子的糖果数值取反,表明待粉碎状态
board[r][c] = board[r][c + 1] = board[r][c + 2] = -v;
todo = true;
}
}
}
// 一列一列扫描
for (int r = 0; r + 2 < rows; ++r) {
for (int c = 0; c < cols; ++c) {
// 取出这个点的绝对值(可能被取反了,所以要绝对值)
int v = Math.abs(board[r][c]);
// 如果连续三个格子是相同的糖果
if (v != 0 && v == Math.abs(board[r + 1][c]) && v == Math.abs(board[r + 2][c])) {
// 把这三个连续格子的糖果数值取反,表明待粉碎状态
board[r][c] = board[r + 1][c] = board[r + 2][c] = -v;
todo = true;
}
}
}
// 经过上面两个 for 循环后,需要粉碎糖果的格子已经变为负数
// 遍历所有格子进行粉碎糖果
if (todo) {
for (int c = 0; c < cols; ++c) { // 从左到右每一列
int wr = rows - 1; // 接收掉落糖果所在行
for (int r = rows - 1; r >= 0; r--) { // 从下往上遍历每一行
if (board[r][c] > 0) {
// 把 (r,c) 的糖果掉落到 (wr,c) 上
// r 和 wr 要么在同一行,要么 r 在上面,因此不用特地找到 wr 的初始行。
board[wr][c] = board[r][c];
wr--; // 往上走一行
}
}
// 如果 wr 没有走到最顶行,说明上面应该全是要粉碎的
while (wr >= 0) {
board[wr--][c] = 0;
}
}
}
// 如果还有需要粉碎的糖果,则再调用一次 candyCrush(board)
// 注意,本次 candyCrush 后是不确定存不存在新的要粉碎的糖果,只能再调用一次 candyCrush
// 如果多调用的 candyCrush 中两个 for 循环都没有把 tod0 标记为 true,则表示结束了
// 因此,本方法都会多调用一次 candyCrush 但不进行粉碎的操作。
return todo ? candyCrush(board) : board;
}
}