419.甲板上的战舰

目标

给你一个大小为 m x n 的矩阵 board 表示甲板,其中,每个单元格可以是一艘战舰 'X' 或者是一个空位 '.' ,返回在甲板 board 上放置的 战舰 的数量。

战舰 只能水平或者垂直放置在 board 上。换句话说,战舰只能按 1 x k(1 行,k 列)或 k x 1(k 行,1 列)的形状建造,其中 k 可以是任意大小。两艘战舰之间至少有一个水平或垂直的空位分隔 (即没有相邻的战舰)。

示例 1:

输入:board = [["X",".",".","X"],[".",".",".","X"],[".",".",".","X"]]
输出:2

示例 2:

输入:board = [["."]]
输出:0

说明:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] 是 '.' 或 'X'

进阶:你可以实现一次扫描算法,并只使用 O(1) 额外空间,并且不修改 board 的值来解决这个问题吗?

思路

这道题的描述很容易让人误解成只允许在'X'的位置上建造战舰,并且满足规则战舰不能相邻,问最多能造多少战舰。

但实际上题目的含义是,有一个棋盘,棋盘上放着战舰,每个战舰由k个'X'表示,战舰的形状是 1*k 或者 k*1,战舰的排放规则是不能相邻,让我们统计战舰数量。

刚开始想到的是使用一个二维数组保存是否访问过,从第一行开始从左到右访问,如果为'X'则向下延伸并将visited置为true。

后来看了题解,可以将原数组的'X'置为其它标记,这样就不用额外的空间了。

进阶的问题是不修改board,根据排列规则,仅统计战舰的起点,即上面与左边不能为'X'。

也有网友使用dfs来处理,子节点是下方与右侧的元素,如果是'X'则改为其它标记,否则返回。其实直接循环遍历更方便一些。

还有网友使用并查集计算连通分量,其实也没有必要。并查集主要用于快速判断元素是否属于同一个集合,统计集合中元素个数,集合元素的合并。这里仅需要连通分量个数,并且连通的关系很简单,纵向或横向,并不像图遍历那样复杂,直接计数即可。

代码

/**
 * @date 2024-06-11 8:40
 */
public class CountBattleships419 {

    /**
     * 网友题解
     * 一次扫描,不使用额外空间,不修改board
     * 统计左上顶点
     * 需满足条件:顶点左侧与上侧不能为'X'
     */
    public int countBattleships_v2(char[][] board) {
        int ans = 0;
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                if (board[i][j] == 'X' &&
                        (j == 0 || board[i][j - 1] != 'X') &&
                        (i == 0 || board[i - 1][j] != 'X')) {
                    ans++;
                }
            }
        }
        return ans;
    }

    /**
     * 看了题解,可以直接将board中的'X'置为'.',这样就不需要额外的visited数组了
     * 一次扫描,但是修改了board
     */
    public int countBattleships_v1(char[][] board) {
        int res = 0;
        int m = board.length;
        int n = board[0].length;
        for (int i = 0; i < m; i++) {
            int j = 0;
            while (j < n) {
                if (board[i][j] == 'X') {
                    res++;
                    int k = i + 1;
                    while (k < m && board[k][j] == 'X') {
                        board[k][j] = '.';
                        k++;
                    }
                    while (j < n && board[i][j] == 'X') {
                        board[i][j] = '.';
                        j++;
                    }
                } else {
                    j++;
                }
            }
        }

        return res;
    }

    /**
     * 一次扫描,但是使用了额外的空间
     */
    public int countBattleships(char[][] board) {
        int res = 0;
        int m = board.length;
        int n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < m; i++) {
            int j = 0;
            while (j < n) {
                if (visited[i][j]) {
                    j++;
                    continue;
                }
                if (board[i][j] == 'X') {
                    res++;
                    int k = i;
                    while (k < m && board[k][j] == 'X') {
                        visited[k][j] = true;
                        k++;
                    }
                    while (j < n && board[i][j] == 'X') {
                        visited[i][j] = true;
                        j++;
                    }
                } else {
                    j++;
                }
            }
        }

        return res;
    }
}

性能

使用额外空间

不使用额外空间