419. 甲板上的战舰
LeetCode: 419. 甲板上的战舰
中 等 \color{#FFB800}{中等} 中等
给你一个大小为
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
的值来解决这个问题吗?
方法1:深度优先搜索 / 广度优先搜索
我们可以先完整遍历整个甲板,每当遍历到 'X'
后,就将其加入 栈 / 队列 中,并使计数加1,将它改为 '.'
以免再次遍历。然后以这个土地为起点,进行 深度 / 广度优先搜索,将所有相邻的 'X'
都改为 '.'
。最后返回计数即可。
以下为 深度优先搜索 的代码。若使用 广度优先搜索 则将栈改为队列即可。
#include <vector>
#include <stack>
using namespace std;
class Solution
{
private:
using dpoint = pair<int, int>;
const dpoint D[4] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
int countBattleships(vector<vector<char>> &board)
{
const int m = board.size(), n = board[0].size();
int ans = 0;
for (int y = 0; y < m; y++)
{
for (int x = 0; x < n; x++)
{
if (board[y][x] == '.')
continue;
ans++;
stack<dpoint> stk;
stk.emplace(y, x);
while (!stk.empty())
{
const auto [_y, _x] = stk.top();
stk.pop();
board[_y][_x] = '.';
for (const auto [dy, dx] : this->D)
{
const int ny = _y + dy, nx = _x + dx;
if (0 <= ny && ny < m && 0 <= nx && nx < n && board[ny][nx] == 'X')
{
stk.emplace(ny, nx);
}
}
}
}
}
return ans;
}
};
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn)。其中,
m
和n
分别为甲板的行数和列数。矩阵中的每一块土地会被遍历多次。 -
空间复杂度: O ( 1 ) O(1) O(1)。我们只需要常量空间来存储若干变量。
参考结果
Accepted
27/27 cases passed (8 ms)
Your runtime beats 74.06 % of cpp submissions
Your memory usage beats 5.15 % of cpp submissions (8.8 MB)
方法2:枚举起点
方法1 是按照正常的 深度 / 广度 遍历方法去完成的,而这题中的土地和平时稍稍会有一点不一样。平时我们在遍历岛屿时,岛屿的形状是不规则的,因此要用 深度 / 广度 去确保岛屿的每一块土地都被遍历完成,而本体中的岛屿,也就是战舰是有固定的形状的:也就是长或宽为1的长条形。由于题目明确给出战舰不会相邻,因此这个形状在地图上的分布有以下特点:
- 关注长条形的某一端点,假设为左上角的顶点(x,y),则
board[y][x]=='X'
; - 这个端点的上方不存在土地或者不属于任何战舰
board[y-1][x]=='.'
。 - 这个端点的左侧不存在土地或者不属于任何战舰
board[y][x-1]=='.'
;
由上方的三个条件,我们就可以在完整遍历甲板的过程中,去枚举每艘战舰的左上顶点来记录战舰的个数,同时也不需要修改矩阵 board
去记录哪些土地被遍历过。
#include <vector>
using namespace std;
class Solution
{
private:
using dpoint = pair<int, int>;
const dpoint D[4] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
int countBattleships(vector<vector<char>> &board)
{
const int m = board.size(), n = board[0].size();
int ans = 0;
for (int y = 0; y < m; y++)
{
for (int x = 0; x < n; x++)
{
if (board[y][x] == 'X' && (y == 0 || board[y - 1][x] == '.') && (x == 0 || board[y][x - 1] == '.'))
ans++;
}
}
return ans;
}
};
复杂度分析
-
时间复杂度: O ( m n ) O(mn) O(mn)。其中,
m
和n
分别为甲板的行数和列数。矩阵中的每一块土地只会被遍历一次。 -
空间复杂度: O ( 1 ) O(1) O(1)。我们只需要常量空间来存储若干变量。
参考结果
Accepted
27/27 cases passed (8 ms)
Your runtime beats 74.06 % of cpp submissions
Your memory usage beats 35.84 % of cpp submissions (7.9 MB)