Given a matrix consists of 0 and 1, find the distance of the nearest 0 for each cell.
The distance between two adjacent cells is 1.
Example 1:
Input:
0 0 0 0 1 0 0 0 0
Output:
0 0 0 0 1 0 0 0 0
Example 2:
Input:
0 0 0 0 1 0 1 1 1
Output:
0 0 0 0 1 0 1 2 1
Note:
- The number of elements of the given matrix will not exceed 10,000.
- There are at least one 0 in the given matrix.
- The cells are adjacent in only four directions: up, down, left and right.
這道題給了我們一個只有0和1的矩陣,讓我們求每一個1到離其最近的0的距離,其實也就是求一個距離場,而求距離場那么BFS將是不二之選。剛看到此題時,我以為這跟之前那道 Shortest Distance from All Buildings 是一樣的,從每一個0開始遍歷,不停的更新每一個1的距離,但是這樣寫下來TLE了。后來我又改變思路,從每一個1開始BFS,找到最近的0,結果還是TLE,氣死人。后來逛論壇發現思路是對的,就是寫法上可以進一步優化,我們可以首先遍歷一次矩陣,將值為0的點都存入queue,將值為1的點改為INT_MAX。之前像什么遍歷迷宮啊,起點只有一個,而這道題所有為0的點都是起點,這想法,叼!然后開始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; } };
在史蒂芬大神的帖子中,他提出了一種變型的方法,沒有再區分左上右下,而是每次都跟左邊相比,但是需要每次把矩陣旋轉90度。他用python寫的解法異常的簡潔,貌似python中可以一行代碼進行矩陣旋轉,但是貌似C++沒有這么叼,矩陣旋轉寫起來還是需要兩個for循環,寫出來估計也不短,這里就不寫了,有興趣的童鞋可以自己試試寫一下,可以貼到留言板上哈~
參考資料:
https://leetcode.com/problems/01-matrix/
https://leetcode.com/problems/01-matrix/discuss/101021/java-solution-bfs
https://leetcode.com/problems/01-matrix/discuss/101039/java-33ms-solution-with-two-sweeps-in-on
https://leetcode.com/problems/01-matrix/discuss/101023/18-line-c-dp-solution-on-easy-to-understand
