算法题 棋盘上的战舰

419. 棋盘上的战舰

问题描述

给你一个 m x n 的棋盘 board,它包含两种字符:

  • 'X':表示战舰的一部分
  • '.':表示空位

战舰用 'X' 表示,且满足以下规则:

  1. 舰队只能水平或垂直放置
  2. 舰队之间至少有一个水平或垂直的空位分隔
  3. 棋盘上没有相邻的舰队(即舰队不会对角相邻)

给你一个有效的战舰放置图,计算棋盘上有多少舰队。

示例

输入: board = [["X",".",".","X"],
              [".",".",".","X"],
              [".",".",".","X"]]
输出: 2
解释: 有两舰队,一个水平放置,一个垂直放置。

算法思路

核心

  1. 舰队特性:舰队是连续的 'X',只能水平或垂直
  2. 关键:每个舰队有且只有一个"头部"
  3. 头部定义:舰队中最靠上、最靠左的 'X'

方法:计数舰队头部

  1. 遍历棋盘每个位置
  2. 当遇到 'X' 时,检查它是否是舰队的头部
  3. 头部判断条件
    • 左边没有 'X'(或在最左边)
    • 上边没有 'X'(或在最上边)
  4. 满足条件的 'X' 就是一舰队的头部,计数加1

为什么这个方法正确?

  • 每舰队有且只有一个头部(最左最上的X)
  • 每个头部对应一完整的舰队
  • 不会重复计数,也不会遗漏

代码实现

class Solution {
    /**
     * 计算棋盘上舰队的数量
     * 
     * @param board m x n 的字符数组,'X'表示战舰,'.'表示空位
     * @return 舰队的数量
     */
    public int countBattleships(char[][] board) {
        int m = board.length;        // 棋盘行数
        int n = board[0].length;     // 棋盘列数
        int count = 0;               // 舰队计数
        
        // 遍历棋盘的每个位置
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 如果当前位置是舰队的一部分
                if (board[i][j] == 'X') {
                    // 检查是否是舰队的头部(最左最上的X)
                    boolean isHead = true;
                    
                    // 检查左边是否有X(同一行)
                    if (j > 0 && board[i][j-1] == 'X') {
                        isHead = false;  // 不是头部,因为左边有X
                    }
                    
                    // 检查上边是否有X(同一列)
                    if (i > 0 && board[i-1][j] == 'X') {
                        isHead = false;  // 不是头部,因为上边有X
                    }
                    
                    // 如果是头部,计数加1
                    if (isHead) {
                        count++;
                    }
                }
            }
        }
        
        return count;
    }
}

优化(更简洁)

class Solution {
    /**
     * 优化:代码更简洁
     * 
     * @param board 棋盘
     * @return 舰队数量
     */
    public int countBattleships(char[][] board) {
        int count = 0;
        int m = board.length;
        int n = board[0].length;
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'X' && 
                    (i == 0 || board[i-1][j] != 'X') &&  // 上边没有X或在边界
                    (j == 0 || board[i][j-1] != 'X')) {  // 左边没有X或在边界
                    count++;
                }
            }
        }
        
        return count;
    }
}

算法分析

  • 时间复杂度:O(m × n)

    • 需要遍历棋盘的每个位置一次
    • 每个位置的检查是 O(1)
  • 空间复杂度:O(1)

    • 只使用常数额外空间
    • 没有使用额外的数据结构
  • 关键

    • 避免了DFS/BFS的复杂实现
    • 一次遍历解决问题
    • 空间效率最高

算法过程

输入:

board = [["X",".",".","X"],
         [".",".",".","X"],
         [".",".",".","X"]]

遍历过程

位置左边有X上边有X是否头部说明
(0,0)X否(边界)否(边界)左上角X,是头部
(0,1).---空位,跳过
(0,2).---空位,跳过
(0,3)X否(边界)右上角X,左边和上边都没有X
(1,0).---空位,跳过
(1,1).---空位,跳过
(1,2).---空位,跳过
(1,3)X是(board[0][3]==‘X’)上边有X,不是头部
(2,0).---空位,跳过
(2,1).---空位,跳过
(2,2).---空位,跳过
(2,3)X是(board[1][3]==‘X’)上边有X,不是头部

结果:count = 2

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    char[][] board1 = {
        {'X',' ','.','X'},
        {'.','.','.','X'},
        {'.','.','.','X'}
    };
    System.out.println("Test 1: " + solution.countBattleships(board1)); // 2
    
    // 测试用例2:单艘战舰
    char[][] board2 = {
        {'X','X','X'}
    };
    System.out.println("Test 2: " + solution.countBattleships(board2)); // 1
    
    // 测试用例3:没有战舰
    char[][] board3 = {
        {'.','.','.'},
        {'.','.','.'}
    };
    System.out.println("Test 3: " + solution.countBattleships(board3)); // 0
    
    // 测试用例4:多艘战舰
    char[][] board4 = {
        {'X','.','X'},
        {'X','.','X'},
        {'X','.','X'}
    };
    System.out.println("Test 4: " + solution.countBattleships(board4)); // 2
    
    // 测试用例5:1x1战舰
    char[][] board5 = {
        {'X'}
    };
    System.out.println("Test 5: " + solution.countBattleships(board5)); // 1
    
    // 测试用例6:复杂布局
    char[][] board6 = {
        {'X','X','.','X'},
        {'.','.','.','.'},
        {'X','.','X','X'},
        {'X','.','.','.'}
    };
    System.out.println("Test 6: " + solution.countBattleships(board6)); // 3
    // 水平战舰(0,0-1),垂直战舰(2-3,0),水平战舰(2,2-3)
}

关键点

  1. 头部定义的正确性

    • 水平舰队:最左边的 'X'
    • 垂直舰队:最上边的 'X'
    • L形舰队:最左最上的 'X'
  2. 边界条件处理

    • 最左边的列:左边没有 'X'
    • 最上边的行:上边没有 'X'
    • 使用 i == 0j == 0 判断边界
  3. 问题约束的利用

    • 舰队不相邻,确保不会误判
    • 只能水平或垂直,简化了头部判断
  4. 为什么不需要标记已访问?

    • 因为只通过位置关系判断头部
    • 不需要防止重复访问

常见问题

  1. 为什么不用DFS/BFS?

    • 可以用,但需要额外空间标记已访问
    • 时间复杂度相同,但空间复杂度O(mn)
    • 本方法更优
  2. 如果舰队可以对角相邻怎么办?

    • 本题保证不会对角相邻
    • 如果允许,需要修改判断逻辑
  3. 算法的直观理解

    • 想象从左到右、从上到下扫描棋盘
    • 每当看到一个 'X',就问:“这是不是一新舰队的开始?”
    • 如果左边和上边都没有 'X',那就是新舰队的开始
  4. 与岛屿数量问题的区别

    • 岛屿问题需要DFS/BFS找连通分量
    • 本题利用舰队的特殊形状,可以用更简单的方法
  5. 扩展

    • 如果舰队可以L形、T形等复杂形状
    • 还能用头部计数法吗?
    • 可以,只要定义好"头部"概念
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值