码迷,mamicode.com
首页 > 其他好文 > 详细

LeetCode——542. 01 矩阵

时间:2020-02-08 13:27:06      阅读:72      评论:0      收藏:0      [点我收藏+]

标签:出队   discus   动态规划   otto   点距   现在   ref   提高   不用   

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:
输入:

0 0 0
0 1 0
0 0 0

输出:

0 0 0
0 1 0
0 0 0

示例 2:
输入:

0 0 0
0 1 0
1 1 1

输出:

0 0 0
0 1 0
1 2 1

注意:

  1. 给定矩阵的元素个数不超过 10000。
  2. 给定矩阵中至少有一个元素是 0。
  3. 矩阵中的元素只在四个方向上相邻: 上、下、左、右。

我们可以首先遍历一次矩阵,将值为0的点都存入queue,将值为1的点改为INT_MAX。然后开始BFS遍历,从queue中取出一个数字,遍历其周围四个点,如果越界或者周围点的值小于等于当前值加1,则直接跳过。因为周围点的距离更小的话,就没有更新的必要,否则将周围点的值更新为当前值加1,然后把周围点的坐标加入queue,参见代码如下:

解法一:

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}};
        queue<pair<int, int>> q;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (matrix[i][j] == 0) q.push({i, j});
                else matrix[i][j] = INT_MAX;
            }
        }
        while (!q.empty()) {
            auto t = q.front(); q.pop();
            for (auto dir : dirs) {
                int x = t.first + dir[0], y = t.second + dir[1];
                if (x < 0 || x >= m || y < 0 || y >= n || matrix[x][y] <= matrix[t.first][t.second] + 1) continue;
                matrix[x][y] = matrix[t.first][t.second] + 1;
                q.push({x, y});
            }
        }
        return matrix;
    }
};

下面这种解法是参考的qswawrq大神的帖子,他想出了一种二次扫描的解法,从而不用使用BFS了。这种解法也相当的巧妙,我们首先建立一个和matrix大小相等的矩阵res,初始化为很大的值,这里我们用INT_MAX-1,为甚么要减1呢,后面再说。然后我们遍历matrix矩阵,当遇到为0的位置,我们将结果res矩阵的对应位置也设为0,这make sense吧,就不多说了。然后就是这个解法的精髓了,如果不是0的地方,我们在第一次扫描的时候,比较其左边和上边的位置,取其中较小的值,再加上1,来更新结果res中的对应位置。这里就明白了为啥我们要初始化为INT_MAX-1了吧,因为这里要加1,如果初始化为INT_MAX就会整型溢出,不过放心,由于是取较小值,res[i][j]永远不会取到INT_MAX,所以不会有再加1溢出的风险。第一次遍历我们比较了左和上的方向,那么我们第二次遍历就要比较右和下的方向,注意两种情况下我们不需要比较,一种是当值为0时,还有一种是当值为1时,这两种情况下值都不可能再变小了,所以没有更新的必要,参见代码如下:

解法二:

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> res(m, vector<int>(n, INT_MAX - 1));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (matrix[i][j] == 0) res[i][j] = 0;
                else {
                    if (i > 0) res[i][j] = min(res[i][j], res[i - 1][j] + 1);
                    if (j > 0) res[i][j] = min(res[i][j], res[i][j - 1] + 1);
                }
            }
        }
        for (int i = m - 1; i >= 0; --i) {
            for (int j = n - 1; j >= 0; --j) {
                if (res[i][j] != 0 && res[i][j] != 1) {
                    if (i < m - 1) res[i][j] = min(res[i][j], res[i + 1][j] + 1);
                    if (j < n - 1) res[i][j] = min(res[i][j], res[i][j + 1] + 1);
                }
            }
        }
        return res;
    }
};

方法 1:暴力【超时】

想法

按照题目描述暴力枚举

算法

初始化 dist [i] [j]=INT_MAX 对所有的 {i,j} 。
搜索整个矩阵。
如果方格是 0,dist [i] [j]=0,
否则,对于每个为 1 的方格,
搜索整个矩阵
如果某个为 0 的节点,计算到当前方格的距离 abs(k-i)+abs(l-j)。
如果计算的距离小于当前记录的距离,更新。

C++

vector<vector<int> > updateMatrix(vector<vector<int> >& matrix)
{
    int rows = matrix.size();
    if (rows == 0)
        return matrix;
    int cols = matrix[0].size();
    vector<vector<int> > dist(rows, vector<int>(cols, INT_MAX));
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (matrix[i][j] == 0)
                dist[i][j] = 0;
            else {
                for (int k = 0; k < rows; k++)
                    for (int l = 0; l < cols; l++)
                        if (matrix[k][l] == 0) {
                            int dist_01 = abs(k - i) + abs(l - j);
                            dist[i][j] = min(dist[i][j], abs(k - i) + abs(l - j));
                        }
            }
        }
    }
    return dist;
}

复杂度分析

复杂度分析

时间复杂度:O((r?c)^2),对于每个为 1 的节点都需要遍历一遍整个矩阵。
空间复杂度:O(r?c),除去 vector<vector > dist 外不需要更多的额外空间开销。

方法 2:广度优先搜索

想法

一种更好的暴力:

搜寻整个矩阵太过于浪费,因此我们使用广度优先搜索去限制搜索空间。只要 0 出现在广度优先搜索时,我们就知道这个 0 是最近的。因此,只需要考虑下一个 1。

再次考虑:

但在这种方法中,我们只能够更新一个 1 的距离,这可能在某种程度上,更大程度提高方法 1 的复杂度。但是,如果我们从 0 开始广度优先搜索并且更新路径上所有的 1,这会优化我们的算法。

算法

对于广度优先搜索,保存一个队列 q 维护下一个需要检查的点。

我们将所有的 0 放入 q 中。

初始地,对于每个为 0 的节点距离都是 1,对于所有的 1 都是 INT_MAX,会根据广搜的结果更新。

弹出队列中的元素,检查它的邻居。如果对于邻居 {i,j} 新计算的距离更小,将 {i,j} 加入队列 q 中同时更新距离 dist[i][j]。

C++

vector<vector<int> > updateMatrix(vector<vector<int> >& matrix)
{
    int rows = matrix.size();
    if (rows == 0)
        return matrix;
    int cols = matrix[0].size();
    vector<vector<int> > dist(rows, vector<int>(cols, INT_MAX));
    queue<pair<int, int> > q;
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            if (matrix[i][j] == 0) {
                dist[i][j] = 0;
                q.push({ i, j }); //Put all 0s in the queue.
            }

    int dir[4][2] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
    while (!q.empty()) {
        pair<int, int> curr = q.front();
        q.pop();
        for (int i = 0; i < 4; i++) {
            int new_r = curr.first + dir[i][0], new_c = curr.second + dir[i][1];
            if (new_r >= 0 && new_c >= 0 && new_r < rows && new_c < cols) {
                if (dist[new_r][new_c] > dist[curr.first][curr.second] + 1) {
                    dist[new_r][new_c] = dist[curr.first][curr.second] + 1;
                    q.push({ new_r, new_c });
                }
            }
        }
    }
    return dist;
}

复杂度分析

时间复杂度:O(r?c),因为每个节点只会加入队列一次,不会多次加入。
空间复杂度:O(r?c),比方法 1 需要多一个队列开销。

方法 3:动态规划

想法

对于一个节点来说,它到 0 的距离可以通过邻居的最近距离计算,在这种情况下最近距离是邻居的距离 + 1。因此,这就让我们想到了动态规划!

对于每个 1,到 0 的最短路径可能从任意方向。所以我们需要检查所有 4 个方向。在从上到下的迭代中,我们需要检查左边和上方的最短路径;我们还需要另一个从下往上的循环,检查右边和下方的方向。

算法

。 从上至下、从左至右迭代整个矩阵:

? 。 更新

? 。dist [i] [j] = min(dist [i] [j], min(dist [i] [j-1], dist [i-1] [j])+1)

最近距离考虑上方邻居和左侧邻居距离 + 1,这在前面的迭代中已经计算完成。

。从下到上、从右至左迭代整个矩阵:

? 。更新

? 。dist [i] [j]=min(dist [i] [j],min(dist [i] [j+1],dist[i+1] [j])+1)

最近距离考虑下方邻居和右侧邻居距离 + 1,这在前面的迭代中已经计算完成。

C++

vector<vector<int> > updateMatrix(vector<vector<int> >& matrix)
{
    int rows = matrix.size();
    if (rows == 0)
        return matrix;
    int cols = matrix[0].size();
    vector<vector<int> > dist(rows, vector<int>(cols, INT_MAX - 100000));

    //First pass: check for left and top
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            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);
            }
        }
    }

    //Second pass: check for bottom and right
    for (int i = rows - 1; i >= 0; i--) {
        for (int j = cols - 1; j >= 0; j--) {
            if (i < rows - 1)
                dist[i][j] = min(dist[i][j], dist[i + 1][j] + 1);
            if (j < cols - 1)
                dist[i][j] = min(dist[i][j], dist[i][j + 1] + 1);
        }
    }

    return dist;
}

复杂度分析

时间复杂度:O(r?c),两边扫描各为 cr?c。
空间复杂度:O(r?c),除了 dist vector<vector< int > > 无需额外空间。

每个非0点到0的距离,跟它上下左右到0的距离有关,所以第一次遍历先将左上两个位置的非0点最小者加1更改。

这样就是从左上往右下看,非0点到0的最短距离,但是这样还不知道从右下到左上看的最短距离。

所以再从右下到左上遍历,注意这次更新除了要取右下两个点的最短距离之外,还要跟当前位置的点做一次最小值比较,因为右下可能距离更远。

class Solution:
    def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                l,t= 10001,10001
                if matrix[i][j] != 0:
                    if i > 0:
                        t = matrix[i - 1][j]
                    
                    if j > 0:
                        l = matrix[i][j - 1]
                    
                    matrix[i][j] = min(l,t) + 1
        
        for i in range(len(matrix) - 1, -1 ,-1):
            for j in range(len(matrix[0]) - 1, -1, -1):
                r,b = 10001,10001
                if matrix[i][j] != 0:
                    if i < len(matrix) - 1:
                        b = matrix[i + 1][j]

                    if j < len(matrix[0]) - 1:
                        r = matrix[i][j + 1]

                    matrix[i][j] = min(matrix[i][j], min(r,b) + 1)
        return matrix

LeetCode——542. 01 矩阵

标签:出队   discus   动态规划   otto   点距   现在   ref   提高   不用   

原文地址:https://www.cnblogs.com/wwj99/p/12275886.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!