1、给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。两个相邻元素间的距离为 1 。
示例:输入:mat = [[0,0,0],[0,1,0],[0,0,0]] 输出:[[0,0,0],[0,1,0],[0,0,0]]
对于这个题我们首先可以想到广度优先搜索。
(1)BFS
代码:
class Solution {
public:
static constexpr int dirs[4][2]{{-1,0},{1,0},{0,-1},{0,1}}; //设置BFS的移动方向(这里的设置方式可以学习)
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
int m = mat.size(),n = mat[0].size();
vector<vector<int>> dist (m,vector<int>(n)); //diste矩阵,记录距离
vector<vector<int>> seen (m,vector<int>(n)); //记录0所在位置,同时在计算了非0数与0的距离后排除非0数
queue<pair<int,int>>q; //队列中加入0元素
for(int i = 0;i<m;++i){
for(int j = 0;j<n;++j){
if(mat[i][j]==0){
q.emplace(i,j); //把0全部放入q矩阵,记录坐标
seen[i][j]=1; //0不参与距离的计算
}
}
}
while(! q.empty()){
auto[i,j]=q.front(); //以q中的0坐标为基准
q.pop();
for(int d = 0;d<4;++d){ //进行广度有限搜索
int ni = i+dirs[d][0];
int nj = j+dirs[d][1];
if(ni>=0 && ni<m && nj>= 0 && nj<n && !seen[ni][nj]){ //非0且没有计算过的坐标进行计算并标记
dist[ni][nj] = dist[i][j] +1;
q.emplace(ni,nj);
seen[ni][nj]=1;
}
}
}
return dist;
}
};
时间复杂度为O(mn),空间复杂度为O(mn)。
(2)动态规划
还可以使用动态规划,也就是把问题进行拆解。比如一直保持一个水平方向和一个垂直方向的移动,最后得到最小距离。
class Solution {
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
// 初始化动态规划的数组,所有的距离值都设置为一个很大的数
vector<vector<int>> dist(m, vector<int>(n, INT_MAX / 2));
// 如果 (i, j) 的元素为 0,那么距离为 0
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == 0) {
dist[i][j] = 0;
}
}
}
// 只有 水平向左移动 和 竖直向上移动,注意动态规划的计算顺序
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (i - 1 >= 0) {
dist[i][j] = min(dist[i][j], dist[i - 1][j] + 1);
}
if (j - 1 >= 0) {
dist[i][j] = min(dist[i][j], dist[i][j - 1] + 1);
}
}
}
// 只有 水平向左移动 和 竖直向下移动,注意动态规划的计算顺序
for (int i = m - 1; i >= 0; --i) {
for (int j = 0; j < n; ++j) {
if (i + 1 < m) {
dist[i][j] = min(dist[i][j], dist[i + 1][j] + 1);
}
if (j - 1 >= 0) {
dist[i][j] = min(dist[i][j], dist[i][j - 1] + 1);
}
}
}
// 只有 水平向右移动 和 竖直向上移动,注意动态规划的计算顺序
for (int i = 0; i < m; ++i) {
for (int j = n - 1; j >= 0; --j) {
if (i - 1 >= 0) {
dist[i][j] = min(dist[i][j], dist[i - 1][j] + 1);
}
if (j + 1 < n) {
dist[i][j] = min(dist[i][j], dist[i][j + 1] + 1);
}
}
}
// 只有 水平向右移动 和 竖直向下移动,注意动态规划的计算顺序
for (int i = m - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (i + 1 < m) { //一定要注意这个边界,防止溢出
dist[i][j] = min(dist[i][j], dist[i + 1][j] + 1);
}
if (j + 1 < n) {
dist[i][j] = min(dist[i][j], dist[i][j + 1] + 1);
}
}
}
return dist;
}
};
最后得到时间复杂度为O(mn),但空间复杂度减少为O(1)。这里除了答案数组dist以外,没有用到额外的数组。因此动态规划在性能上是优于BFS的。但也可以把代码进一步优化,比如只留下左上和右下方向的动态规划。时间空间复杂度差不多但是代码上更加简洁。
2、在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。示例:
输入:grid = [[2,1,1],[1,1,0],[0,1,1]] 输出:4
很容易可以想到广度优先搜索,搜到了新鲜橘子就让他变腐烂。
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int row=grid.size();
int col=grid[0].size();
int res=0;
vector<int>dx={-1,0,0,1};//辅助定位即将被腐烂的橘子的横坐标
vector<int>dy={0,1,-1,0};//辅助定位即将被腐烂的橘子的纵坐标,对应构成腐烂橘子的四个污染方向
queue<pair<int,int>>rot ;
for(int i=0;i<row;++i)//将腐烂橘子一一压入队列
for(int j=0;j<col;++j)
if(grid[i][j]==2)
rot.push({i,j});
while(!rot.empty())
{
int vol=rot.size();//标记队列内腐烂橘子个数
for(int i=0;i<vol;++i)
{
pair<int,int> fir=rot.front();//取出首个腐烂橘子
rot.pop();
for(int j=0;j<4;++j)//进行四个方向污染
{
int x=fir.first+dx[j],y=fir.second+dy[j];
if(x>=0&&x<row&&y>=0&&y<col&&grid[x][y]==1)//判断是否存在新鲜橘子
{
grid[x][y]=2;
rot.push({x,y});
}
}
}
if(!rot.empty())//如果为空表示一开始就没有腐烂橘子,返回0分钟
res++;//每次取出队列所有橘子时间加1,同时压入被污染的新一批橘子
}
for(int i=0;i<row;++i)//检查是否还有新鲜橘子
for(int j=0;j<col;++j)
if(grid[i][j]==1)
return -1;
return res;
}
};
最后得到时间复杂度O(mn),空间复杂度O(1),在数组本身进行修改,就设置了一个队列而无其他消耗空间的组件。