【LeetCode 每日一题】2257. 统计网格图中没有被保卫的格子数
Problem: 2257. 统计网格图中没有被保卫的格子数
文章目录
- 整体思路
 - 完整代码
 - 时空复杂度
 - 时间复杂度:O(M*N + G*(M+N))
 - 空间复杂度:O(M*N)
 
整体思路
这段代码旨在解决一个二维网格问题:在一个 m x n 的房间里,给定守卫 guards 和墙壁 walls 的位置,计算出所有未被守卫监视且不是障碍物(守卫或墙壁)的空格子数量。
守卫的监视范围是其所在行和列的四个方向(上、下、左、右),直到遇到边界、另一个守卫或一堵墙为止。
该算法采用了一种非常直观的 模拟 (Simulation) 或 射线投射 (Ray Casting) 的方法。
-  
第一步:初始化网格状态
- 算法创建了一个与房间大小相同的 
m x n的二维数组guarded。这个数组用来标记网格中每个格子的状态:0: 初始状态,表示一个空的、未被监视的格子。-1: 表示一个障碍物(守卫或墙壁)。1: 表示一个被监视的格子。
 - 通过遍历 
guards和walls数组,将所有守卫和墙壁的位置在guarded数组中标记为-1。 
 - 算法创建了一个与房间大小相同的 
 -  
第二步:模拟守卫的监视
- 这是算法的核心。它遍历每一个 
guard的位置。 - 对于每个守卫,它再遍历四个基本方向 
DIRS(上、下、左、右)。 - 对于每个方向,它模拟一条从守卫位置出发的“视线”: 
int x = g[0] + dx; int y = g[1] + dy;: 初始化视线的起始点,即守卫旁边的第一个格子。while (0 <= x && x < m && 0 <= y && y < n && guarded[x][y] != -1): 这个while循环是模拟视线延伸的关键。它会一直持续,直到:- 视线超出了房间边界 (
0 <= x && x < m && 0 <= y && y < n不满足)。 - 视线遇到了一个障碍物 (
guarded[x][y] != -1不满足)。 
- 视线超出了房间边界 (
 guarded[x][y] = 1;: 在while循环内部,所有路径上的非障碍物格子都被标记为1(被监视)。x += dx; y += dy;: 将视线沿当前方向延伸一格。
 
 - 这是算法的核心。它遍历每一个 
 -  
第三步:统计未被监视的格子
- 在所有守卫的监视范围都标记完毕后,
guarded数组就完整地反映了整个房间的状态。 - 算法最后通过一个嵌套的 
for循环遍历整个guarded数组。 if (x == 0): 检查每个格子的状态。如果一个格子的状态仍然是0,说明它既不是障碍物,也没有被任何守卫监视到。ans++: 将计数器ans加一。
 - 在所有守卫的监视范围都标记完毕后,
 -  
返回结果
- 遍历结束后,
ans中就存储了所有未被监视的空格子数量。 
 - 遍历结束后,
 
完整代码
class Solution {// 定义四个方向的偏移量:上, 下, 左, 右private static final int[][] DIRS = { { 0, -1 }, { 0, 1 }, { -1, 0 }, { 1, 0 } };/*** 计算网格中未被守卫监视的空格子数量。* @param m       房间的行数* @param n       房间的列数* @param guards  守卫位置的数组* @param walls   墙壁位置的数组* @return 未被监视的空格子数量*/public int countUnguarded(int m, int n, int[][] guards, int[][] walls) {// 步骤 1: 初始化网格状态// 0: 空闲且未被监视, -1: 障碍物 (守卫或墙), 1: 被监视int[][] guarded = new int[m][n];// 将所有守卫的位置标记为障碍物for (int[] g : guards) {guarded[g[0]][g[1]] = -1;}// 将所有墙壁的位置标记为障碍物for (int[] w : walls) {guarded[w[0]][w[1]] = -1;}// 步骤 2: 模拟每个守卫的监视范围for (int[] g : guards) {// 对每个守卫,检查四个方向for (int[] d : DIRS) {int dx = d[0];int dy = d[1];// 从守卫旁边的格子开始int x = g[0] + dx;int y = g[1] + dy;// 沿着该方向一直延伸,直到遇到边界或障碍物while (0 <= x && x < m && 0 <= y && y < n && guarded[x][y] != -1) {// 将路径上的格子标记为“被监视”guarded[x][y] = 1;// 移动到下一个格子x += dx;y += dy;}}}// 步骤 3: 统计未被监视的格子int ans = 0;for (int[] row : guarded) {for (int x : row) {// 如果格子的状态仍然是 0,说明它未被监视if (x == 0) {ans++;}}}return ans;}
}
 
时空复杂度
- 设 
M是行数,N是列数。 - 设 
G是守卫的数量 (guards.length)。 - 设 
W是墙壁的数量 (walls.length)。 
时间复杂度:O(MN + G(M+N))
- 初始化 
guarded数组:创建一个M x N的数组,时间复杂度为 O(M*N)。 - 标记障碍物: 
- 遍历 
guards数组,G次操作,时间为 O(G)。 - 遍历 
walls数组,W次操作,时间为 O(W)。 
 - 遍历 
 - 模拟监视: 
- 外层循环遍历 
G个守卫。 - 中间循环遍历 4 个方向。
 - 内层 
while循环,对于每个守卫的每个方向,最多延伸M(垂直) 或N(水平) 步。 - 因此,处理一个守卫的时间复杂度是 
O(M+N)。 - 处理所有守卫的总时间复杂度是 O(G * (M+N))。
 
 - 外层循环遍历 
 - 统计结果: 
- 最后遍历 
M x N的guarded数组一次,时间复杂度为 O(M*N)。 
 - 最后遍历 
 
综合分析:
 总的时间复杂度是 O(M*N + G + W + G*(M+N) + M*N)。通常 G 和 W 远小于 M*N。所以,复杂度可以简化为 O(MN + G(M+N))。
空间复杂度:O(M*N)
- 主要存储开销:算法创建了一个名为 
guarded的二维整型数组。 - 空间大小:该数组的大小是 
M x N。 - 其他变量:
DIRS是一个小的常量数组,其他变量如dx, dy, x, y, ans等都只占用常数空间。 
综合分析:
 算法所需的额外空间主要由 guarded 状态数组决定。因此,其空间复杂度为 O(M*N)。
参考灵神
