持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
题目描述
给你两个整数 m 和 n 表示一个下标从 0 开始的 m x n 网格图。同时给你两个二维整数数组 guards 和 walls ,其中 且 ,分别表示第 i 个警卫和第 j 座墙所在的位置。
一个警卫能看到 4 个坐标轴方向(即东、南、西、北)的 所有 格子,除非他们被一座墙或者另外一个警卫 挡住 了视线。如果一个格子能被 至少 一个警卫看到,那么我们说这个格子被 保卫 了。
请你返回空格子中,有多少个格子是 没被保卫 的。
提示:
guards[i].length == walls[j].length == 2guards和walls中所有位置 互不相同 。
示例 1:
输入:m = 4, n = 6, guards = [[0,0],[1,1],[2,3]], walls = [[0,1],[2,2],[1,4]]
输出:7
解释:上图中,被保卫和没有被保卫的格子分别用红色和绿色表示。
总共有 7 个没有被保卫的格子,所以我们返回 7 。
示例 2:
输入:m = 3, n = 3, guards = [[1,1]], walls = [[0,1],[1,0],[2,1],[1,2]]
输出:4
解释:上图中,没有被保卫的格子用绿色表示。
总共有 4 个没有被保卫的格子,所以我们返回 4 。
整理题意
在一个 m x n 的网格图中,给了我们 保卫 guards 和 墙 walls 的坐标位置,保卫能够保卫上、下、左、右四个坐标轴方向上的所有格子,但是由于墙的存在,会阻挡保卫的视线,所以墙后的格子保卫看不见,也就保卫不到。问有多少个格子是没有被保卫的。
解题思路分析
习惯性动作,首先观察题目数据范围:
- 矩阵的长和宽都在 以内,且保证格子总数在 以内。
- 保卫和墙的数量在 ,且保证保卫和墙的总数量不会超过格子总数。
题目数据范围较大,如果每次遍历保卫能够保卫的格子,会存在遍历重复的格子情况。
正向思维: 遍历每一个保卫,标记每个保卫能够保卫的格子。
通常情况正向思维暴力解题都会存在较复杂或超时等情况,这时候往往我们需要逆向思考一下。
逆向思维: 遍历每一个格子,考虑该格子是否被保卫,只需上下左右某个方向上存在保卫即可。
一个格子是否被保卫需要考虑四个方向上是否存在保卫,如果我们暴力寻找四个方向上的保卫,同样会存在重复遍历的问题,导致 TLE 超时。
由于一个格子仅需被一个方向上的保卫看见即可,那我们对整个网格进行四次遍历,每次朝着一个方向遍历,将该方向上能够被保卫的格子标记上,经过四次四个方向的遍历,剩下没有被标记的网格即为无法被保卫的网格。
具体实现
我们以 示例 1 为例来模拟四次遍历的过程:
-
从左到右遍历:
- 遇到保卫时将当前格子标记为 保卫 状态;
- 遇到墙时将当前格子标记为 无法保卫 状态;
- 利用当前格子状态更新所遍历到的格子。
-
从右到左遍历:(操作同上)
-
从上到下遍历:(操作同上)
- 当遇到被标记过的格子直接跳过即可,因为一个格子仅需被一个方向上的保卫看见即可。
- 当遇到被标记过的格子直接跳过即可,因为一个格子仅需被一个方向上的保卫看见即可。
-
从下到上遍历:(操作同上)
-
遍历整个网格,统计未被标记的网格数量,即为没有被保卫的格子数量。
复杂度分析
- 时间复杂度:,
n为网格的宽,m为网格的长,nm为网格的总数量,需遍历四次网格。 - 空间复杂度:,
n为网格的宽,m为网格的长,nm为网格的总数量,仅需标记网格所需的空间。
代码实现
注意:由于代码习惯,将
m和n交换了,n为行,m为列。
class Solution {
public:
int countUnguarded(int n, int m, vector<vector<int>>& guards, vector<vector<int>>& walls) {
//创建 n x m 大小的二维矩阵
int G[n][m];
memset(G, 0, sizeof(G));
//标记保卫位置为 2
int k = guards.size();
for(int i = 0; i < k; i++){
G[guards[i][0]][guards[i][1]] = 2;
}
//标记墙位置为 3
k = walls.size();
for(int i = 0; i < k; i++){
G[walls[i][0]][walls[i][1]] = 3;
}
//矩阵中0表示没有被保卫, 1表示被保卫,2表示保卫,3表示墙
int flag;
for(int i = 0; i < n; i++){
flag = 0;
//从左到右
for(int j = 0; j < m; j++){
//如果遇到墙,将flag标记为0,表示后面的都无法被保卫
if(G[i][j] == 3) flag = 0;
//如果遇到保卫,将flag标记为1,表示后面的可以被保卫
else if(G[i][j] == 2) flag = 1;
//如果是空地
else{
//如果该方格没有被保卫就更新此时的保卫情况
if(G[i][j] != 1) G[i][j] = flag;
}
}
flag = 0;
//从右到左,操作同从左到右
for(int j = m - 1; j >= 0; j--){
if(G[i][j] == 3) flag = 0;
else if(G[i][j] == 2) flag = 1;
else{
if(G[i][j] != 1) G[i][j] = flag;
}
}
}
for(int i = 0; i < m; i++){
flag = 0;
//从上到下
for(int j = 0; j < n; j++){
if(G[j][i] == 3) flag = 0;
else if(G[j][i] == 2) flag = 1;
else{
if(G[j][i] != 1) G[j][i] = flag;
}
}
//从下到上
for(int j = n - 1; j >= 0; j--){
if(G[j][i] == 3) flag = 0;
else if(G[j][i] == 2) flag = 1;
else{
if(G[j][i] != 1) G[j][i] = flag;
}
}
}
//统计没有被保卫的格子( G[i][j] == 0 的格子)
int ans = 0;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
if(G[i][j] == 0) ans++;
}
}
return ans;
}
};
总结
该题的 核心思想在于维护格子四个方向上最近的物体 。维护最近的物体我们只需要朝着一个方向遍历,同时维护一个变量 flag 即可,遇见不同物体修改 flag 的值即可。考虑一个格子是否被保卫需要考虑四个方向上最近的物体是否存在保卫。通过四次四个方向的遍历来确定格子状态,每个格子仅需遍历四次即可确定该格子的保卫情况。
结束语
正在奋斗路上的你,不要怕吃苦。没有什么捷径能让你出类拔萃,没有哪些艰难是白白煎熬。你的每一份经历,不管是顺境还是坎坷,都会增加你生命的厚度。