题目描述

✅ 419. 棋盘上的战舰

image-20260929094921361

image-20260929094921478

题意分析

棋盘用 X 表示战舰格子、. 表示空位。每艘战舰是一条连续的水平线或垂直线,也可以只占一个格子;不同战舰之间不会横向或纵向相邻,需要统计战舰总数。

题目进阶要求只扫描一遍、使用常数额外空间,并且不修改棋盘。利用战舰的直线形状与间隔条件,只统计每艘舰唯一的起点即可满足这些要求。

解法:统计战舰起点

核心思路

[!blue]

给每艘舰选择唯一代表:水平舰取最左格,垂直舰取最上格,单格舰取它自己。这个代表都是一个“上方没有 X,左侧也没有 X”的战舰格子。

对水平舰,除最左格外的所有格子都有左侧相邻的同舰 X,所以都能排除;最左格没有左侧舰身,而上方也不能存在其他战舰的相邻格,因此会被保留。对垂直舰同理,除最上格外都有上方同舰 X,只有最上格被保留。单格舰周围没有相邻舰格,也恰好计数一次。

这样,每艘战舰恰好贡献一个满足条件的位置,所有舰身位置都被排除,统计这些位置就等于统计战舰。形状与互不相邻的保证,是这个一一对应关系成立的依据。

逐格检查当前值、上方和左侧就足够,不需要继续沿舰身搜索。判断始终读取原棋盘,不能为了标记已计数而修改 X,否则后续舰身可能失去用来识别同一艘舰的邻格。

解题步骤

  1. 扫描每个格子,当前不是 X 就跳过。
  2. 若不在第一行且上方是 X,它属于已有垂直舰的舰身,跳过。
  3. 若不在第一列且左侧是 X,它属于已有水平舰的舰身,跳过。
  4. 两个前驱都不存在的 X 是舰首,将计数加一。

第一行没有上方邻格,第一列没有左侧邻格,应先判断边界再读取。全空棋盘没有任何计数位置;代码还直接处理了空棋盘输入。

代码实现

class Solution {
    public int countBattleships(char[][] board) {
        if (board.length == 0 || board[0].length == 0) {
            return 0;
        }

        int count = 0;

        for (int row = 0; row < board.length; row++) {
            for (int col = 0; col < board[0].length; col++) {
                if (board[row][col] != 'X') {
                    continue;
                }

                // 有上方同舰格时是竖向舰身,不重复计数。
                if (row > 0 && board[row - 1][col] == 'X') {
                    continue;
                }

                // 有左侧同舰格时是横向舰身,其余位置才是舰首。
                if (col > 0 && board[row][col - 1] == 'X') {
                    continue;
                }

                count++;
            }
        }

        return count;
    }
}
func countBattleships(board [][]byte) int {
    if len(board) == 0 || len(board[0]) == 0 {
        return 0
    }

    count := 0
    for row := range board {
        for col := range board[row] {
            if board[row][col] != 'X' {
                continue
            }
            // 有上方同舰格时是竖向舰身,不重复计数。
            if row > 0 && board[row-1][col] == 'X' {
                continue
            }
            // 有左侧同舰格时是横向舰身,其余位置才是舰首。
            if col > 0 && board[row][col-1] == 'X' {
                continue
            }
            count++
        }
    }
    return count
}

复杂度分析

  • 时间复杂度:$O(mn)$,其中 $m,n$ 是棋盘的行列数。每个格子只扫描一次,并检查常数个邻格。
  • 空间复杂度:$O(1)$,只维护计数和下标,没有访问表,也不修改棋盘,满足进阶要求。

关键点总结

[!green]

  • 唯一代表是最上或最左的舰首。
  • 邻居边界先判断,再读取格子。
  • 单格战舰自然满足两个前驱都不存在。

易错点总结

[!yellow]

  • 只检查上方:横向舰身被重复统计。
  • 只检查左侧:纵向舰身被重复统计。
  • 上方和左侧同时为 X 才排除:普通直线舰身通常只有一个前驱。
  • 计数后改写棋盘:会破坏后续用原格子判断舰身的依据。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2022/15657902
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!