Leetcode 542. 01 Matrix (python+cpp)

博客详细介绍了LeetCode 542题目的多种解法,包括从0出发的BFS、从1出发的BFS以及双向动态规划。通过优化,将初始的O(m² * n²)复杂度降低到O(mn),并提供了Python和C++的实现代码。作者强调了BFS在处理此类问题中的关键点,以及如何避免重复计算,加深了对多源最短路径问题的理解。

题目

在这里插入图片描述

解法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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值