leetcode
leetcode 601 ~ 650
粉碎糖果

粉碎糖果

难度:

标签:

题目描述

代码结果

运行时间: 69 ms, 内存: 16.1 MB


/*
题目思路:
1. 使用Stream API进行数组转换和处理。
2. 扫描整个糖果板,看是否有三个或三个以上相同的糖果在一行或一列。如果找到,标记这些糖果。
3. 把被标记的糖果消除,并让上面的糖果掉下来填补空缺。
4. 重复上述步骤,直到没有更多的糖果可以被消除。
*/
import java.util.Arrays;
 
public class CandyCrushStream {
    public int[][] candyCrush(int[][] board) {
        int rows = board.length;
        int cols = board[0].length;
        boolean[] toCrushRow = new boolean[rows * cols];
        boolean[] toCrushCol = new boolean[rows * cols];
 
        Arrays.stream(board).flatMapToInt(Arrays::stream).forEach(candy -> {
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols - 2; j++) {
                    if (board[i][j] != 0 && board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2]) {
                        toCrushRow[i * cols + j] = toCrushRow[i * cols + j + 1] = toCrushRow[i * cols + j + 2] = true;
                    }
                }
            }
            for (int j = 0; j < cols; j++) {
                for (int i = 0; i < rows - 2; i++) {
                    if (board[i][j] != 0 && board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j]) {
                        toCrushCol[i * cols + j] = toCrushCol[(i + 1) * cols + j] = toCrushCol[(i + 2) * cols + j] = true;
                    }
                }
            }
        });
 
        boolean toCrush = Arrays.stream(toCrushRow).anyMatch(c -> c) || Arrays.stream(toCrushCol).anyMatch(c -> c);
 
        if (toCrush) {
            for (int j = 0; j < cols; j++) {
                int idx = rows - 1;
                for (int i = rows - 1; i >= 0; i--) {
                    if (!toCrushRow[i * cols + j] && !toCrushCol[i * cols + j]) {
                        board[idx--][j] = board[i][j];
                    }
                }
                while (idx >= 0) {
                    board[idx--][j] = 0;
                }
            }
        }
 
        return toCrush ? candyCrush(board) : board;
    }
}

解释

方法:

这个题解采用了两次遍历的方法来解决粉碎糖果问题。第一次遍历中,对每个非零元素,检查其水平和垂直方向上是否存在连续三个相同的数字。如果存在,则将这些数字标记为待消除状态(取相反数)。第二次遍历中,对每一列进行处理,将非零元素移动到当前列的底部,其余位置填充为0。重复这个过程,直到不存在可消除的相同数字为止。

时间复杂度:

O(m^2 * n^2)

空间复杂度:

O(1)

代码细节讲解

🦆
在算法中,如何确保在每一次循环中,标记待消除状态的数字不会影响正在检查的其他连续三个数字的判断?
在算法中,通过使用数字的绝对值来进行连续性检查,可以确保标记待消除状态的数字不会影响其他连续三个数字的判断。即便数字被标记为负值(待消除状态),其绝对值仍然保持不变,因此在检查时使用绝对值abs可以正确地识别连续的相同数字,避免了标记影响判断的问题。
🦆
为什么选择使用取相反数的方法来标记待消除的数字,而不是使用其他标记方式如特殊值或者布尔数组?
使用取相反数的方法来标记待消除的数字的主要优点是节省空间和简化操作。这种方法不需要额外的存储空间来记录状态(如布尔数组所需的额外空间),同时也避免了使用特殊值可能带来的数值冲突问题(特殊值可能与有效数据冲突)。通过简单地取负值,可以在不改变原有数组结构的基础上,有效地标记和识别待消除的数字。
🦆
在处理列元素移动时,有没有可能出现在上移动过程中误将已标记为待消除的数字当作有效数字处理的情况?
在列元素移动的处理中,算法确保只有大于0的元素(有效数字)才被向下移动。由于待消除的数字被标记为负值,这些数字在移动过程中不会被视为有效数字。因此,在向下移动过程中,不会误将已标记为待消除的数字(负值)当作有效数字处理。这种区分通过检查数字是否大于0来实现。
🦆
这种方法中,如何处理边界条件,例如矩阵的最上行或最左列?
在处理边界条件时,算法通过限制索引范围来确保不会越界。例如,在检查水平连续三个相同数字时,会确保索引j小于n-2,这样在j, j+1, j+2访问时不会超过矩阵的右边界。同样地,检查垂直连续三个相同数字时,会确保索引i小于m-2,以避免在i, i+1, i+2访问时超过矩阵的下边界。这样的索引限制确保了即使是在矩阵的边缘也不会出现越界错误。

相关问题