题目

解法1:BFS
这道题目第一眼看到的感觉应该是用BFS,但是如果直接用BFS对每个1的位置找最近的0的话,复杂度是会到O(m2 * n2), 这样会超时。那么换种思路,如果从0的位置出发去更新1呢,这样会很大程度减少复杂度,具体如下:
- 构建一个与matrix相同形状的dist矩阵,每个位置的值代表与0的最短距离
- 首先遍历一次matrix,将所有0的位置压入队列,并且所有0的位置相应dist中的值设为0
- 开始对队列中的元素进行BFS,从0出发,假设当前的位置为[i][j],向四周扩散找1的位置为[x][y],如果dist[x][y]>dist[i][j]+1的话,那么更新dist[x][y]的值,同时把这个[x][y]押入到队列
这样乍一看复杂度感觉是一样的,其实则不然,因为只有满足条件的位置才会被压入到队列中,所以复杂度会大大减小。leetcode官方给出的复杂度是O(mn),我个人觉得不能保证是O(mn),但一定比O(m2 * n2)小很多
python代码如下:
class Solution:
def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
# BFS解法
m,n = len(matrix),len(matrix[0])
dist = [[float('inf')]*n for _ in range(m)]
q = collections.deque()
for i in range(m):
for j in range(n):
if matrix[i][j] == 0:
dist[i][j] = 0
q.append((i,j))
dirs = [[0,1],[0,-1],[-1,0],[1,0]]
while q:
curr = q.popleft()
for d in dirs:
x = curr[0]+d[0]
y = curr[1]+d[1]
if x>=0 and x<m and y>=0 and y<n and matrix[x][y]==1:
if dist[x][y]>dist[curr[0]][curr[1]]+1:
dist[x][y] = dist[curr[0]][curr[1]]+1
q.append((x,y))
return dist
解法2:BFS
上面第一种BFS是从0出发,然后不断向外更新最短距离。在二刷的时候,自己想了一种从1出发的BFS。就是从每个1出发,一圈圈向外扩张,找到0时计算步数。对于这种方法就是每个1的位置进行一次BFS。值得注意的是,这边的BFS必须加visited防止成环,而第一种BFS不需要,因为第一种BFS只有在距离更新的情况下才会将当前位置加入队列,否则不会。这样已经被更新过的节点不会被重复加入,代码如下:
class Solution:
def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
def bfs(i,j):
q = collections.deque()
q.append((i,j))
visited = set()
visited.add((i,j))
step = -1
flag = False
while q:
if flag:
break
_len = len(q)
step += 1
for _ in range(_len):
curr = q.popleft()
if matrix[curr[0]][curr[1]] == 0:
flag = True
break
dirs = [[0,1],[0,-1],[-1,0],[1,0]]
for d in dirs:
x = curr[0]+d[0]
y = curr[1]+d[1]
if 0<=x<m and 0<=y<n and (x,y) not in visited:
visited.add((x,y))
q.append((x,y))
return step
m = len(matrix)
n = len(matrix[0])
ans = [[0]*n for _ in range(m)]
for i in range(m):
for j in range(n):
if matrix[i][j] == 1:
ans[i][j] = bfs(i,j)
return ans
解法3:双向动态规划
其实用动态规划解的思路很清晰,某个位置的距离最小值应该是min(dist[i][j],四个方向的最小值+1).但问题就在于,动态规划在进行遍历的需要具有方向性,并不能同时更新四个方向的值。所以需要使用两次不同方向的动态规划,一次从左上到右下,一次从右下到左上,两次一起就可以完整的更新整个矩阵。这边要注意的点是在左下时,对于两个元素分别根据情况判断。在二刷的时候,我就想直接拿当前元素,和相应的两个元素一起进行比较,这样是不行的,因为对于右上角和左下角的元素来说,这么做会数组访问越界,所以就只有用判断i和j的大小来进行分别更新周围元素
python代码如下:
class Solution:
def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
m,n = len(matrix),len(matrix[0])
dist = [[float('inf')]*n for _ in range(m)]
for i in range(m):
for j in range(n):
if matrix[i][j]==0:
dist[i][j] = 0
else:
if i>0:
dist[i][j] = min(dist[i][j],dist[i-1][j]+1)
if j>0:
dist[i][j] = min(dist[i][j],dist[i][j-1]+1)
for i in range(m-1,-1,-1):
for j in range(n-1,-1,-1):
if i<m-1:
dist[i][j] = min(dist[i][j],dist[i+1][j]+1)
if j<n-1:
dist[i][j] = min(dist[i][j],dist[i][j+1]+1)
return dist
C++版本BFS
class Solution {
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
// 注意C++中最大整数值的表达
vector<vector<int>> dist(m,vector<int>(n,INT_MAX));
queue<pair<int,int>> q;
for (int i=0;i<m;++i){
for (int j=0;j<n;++j){
if (matrix[i][j]==0){
dist[i][j] = 0;
q.push({i,j});
}
}
}
// 注意这边dirs的表达,可以直接这样初始化
vector<pair<int,int>> dirs = {{-1,0},{1,0},{0,-1},{0,1}};
while (!q.empty()){
pair<int,int> curr = q.front();
q.pop();
for (auto d:dirs){
int x = curr.first+d.first;
int y = curr.second+d.second;
if (x>=0 && x<m && y>=0 && y<n && matrix[x][y]==1){
if (dist[x][y] > dist[curr.first][curr.second]+1){
dist[x][y] = dist[curr.first][curr.second]+1;
q.push({x,y});
}
}
}
} return dist;
}
};
二刷
其实这道题目的本质是一个Multi Source Shortest Path/bfs 的问题。关于相关定义可以看这边:https://www.geeksforgeeks.org/multi-source-shortest-path-in-unweighted-graph/
在开始bfs的时候,我们不是只放一个节点,而是放符合条件的多个节点,然后再进行标准的bfs。为什么这样是对的?总结如下:it will visit the vertices which are at a distance of 1 from all source vertices, then at a distance of 2 from all source vertices and so on and so forth.
我们把所有0的位置放到队列,对于刚开始bfs,访问到的所有1的位置都会是从最近的那个0出发的,后面再碰到这个1的时候,相应的距离一定会比前面碰到的时候大,这也是为什么每个位置只需访问一次。
这个是对bfs的到最短路径更加深层次的理解,看前面我写的bfs,实际上那个距离的判断条件只会在第一次起到效果,后面都是没有用的。
c++版本的multi-scource
class Solution {
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
int m = mat.size();
int n = mat[0].size();
queue<pair<int,int>> q;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(mat[i][j] == 0){
q.push(make_pair(i,j));
}else{
mat[i][j] = -1;
}
}
}
vector<pair<int,int>> dirs{{0,1},{0,-1},{-1,0},{1,0}};
while(!q.empty()){
auto curr = q.front();
// cout << curr.first << " " << curr.second << endl;
q.pop();
for(auto& d : dirs){
int x = curr.first + d.first;
int y = curr.second + d.second;
if(x < 0 || y < 0 || x >= m || y >= n || mat[x][y] != -1) continue;
q.push(make_pair(x,y));
mat[x][y] = mat[curr.first][curr.second] + 1;
}
}
return mat;
}
};
当然这道题目用dp也可以,但是感觉不是解决这类问题的正统方法
关于Multi Source Shortest Path/bfs的问题,下面还有两个:
http://t.csdn.cn/FOhUB
https://blog.csdn.net/qq_37821701/article/details/104231821?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165516892916782391831836%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165516892916782391831836&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-1-104231821-null-null.nonecase&utm_term=walls&spm=1018.2226.3001.4450
博客详细介绍了LeetCode 542题目的多种解法,包括从0出发的BFS、从1出发的BFS以及双向动态规划。通过优化,将初始的O(m² * n²)复杂度降低到O(mn),并提供了Python和C++的实现代码。作者强调了BFS在处理此类问题中的关键点,以及如何避免重复计算,加深了对多源最短路径问题的理解。
799

被折叠的 条评论
为什么被折叠?



