You are given an m x n
integer matrix grid
where each cell is either 0
(empty) or 1
(obstacle). You can move up, down, left, or right from and to an empty cell in one step.
Return the minimum number of steps to walk from the upper left corner (0, 0)
to the lower right corner (m - 1, n - 1)
given that you can eliminate at most k
obstacles. If it is not possible to find such walk return -1
.
Example 1:
Input: grid = [[0,0,0],[1,1,0],[0,0,0],[0,1,1],[0,0,0]], k = 1
Output: 6
Explanation:
The shortest path without eliminating any obstacle is 10.
The shortest path with one obstacle elimination at position (3,2) is 6. Such path is (0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2).
Example 2:
Input: grid = [[0,1,1],[1,1,1],[1,0,0]], k = 1
Output: -1
Explanation: We need to eliminate at least two obstacles to find such a walk.
Constraints:
m == grid.length
n == grid[i].length
1 <= m, n <= 40
1 <= k <= m * n
grid[i][j]
is either0
or1
.grid[0][0] == grid[m - 1][n - 1] == 0
這道題說是給了一個 m by n 的二維數組迷宮,只有兩個數字0和1,其中0表示空地,即可以通行,1表示障礙物。在一般的迷宮題目中,障礙物是不能通行的,但是這里起始時給了k個清除障礙物的機會,障礙物被清除后就可以通行了,現在讓找從左上角到右下角的最短的路徑長度。可能有的童鞋看到是從左上角到右下角,是不是每次只要向右或者向下移動就能得到最短的路徑,但其實不是的,由於障礙物的存在,所以本質上還是個迷宮,雖然有移除障礙物的機會,但有可能障礙物的個數會遠大於移除的次數,所以回頭路可能是無法避免的。迷宮遍歷求最短路徑,刷題老司機們應該都會立馬條件反射般的想到應該是用廣度優先遍歷 Breadth-first Search。對於一般的 BFS 來說,狀態就只包括位置信息,但這里的每一個狀態不僅僅包括位置,還應該包括當前剩余的除障礙次數,這兩個信息放在一起組成了狀態,同時為了提高查找速度,博主將每個狀態編碼成字符串,放到 HashSet 以便查重,但不幸的是,這個寫法最終還是超時了 Time Limit Exceeded,看來這道題的 OJ 對於時間的要求還是蠻苛刻的,不過也算對得起其 Hard 的身價。
既然傳統的 BFS 寫法超時了,就要想想怎么樣才能進行優化,那么首先就得分析清楚到底的哪個部分比較耗時。目前由於每個狀態包含了兩個信息,位置和剩余的除障礙次數,就是說同一個位置可以訪問多次,只要剩余的去除障礙次數不同就是不同的狀態,但是這樣的話,有可能會存在大量的重復計算,剩余的去除障礙次數少的路徑一定步數更多,因為之前就到過這個位置了,所以剩余的去除障礙次數少的那條路徑就可以直接砍掉,不用再往下計算浪費時間了,同時 BFS 保證了最短的距離,所以不用擔心得不到正確的結果。這樣的話就要知道每一個位置的最大剩余去除障礙次數,可以建立一個跟原數組相同大小的 visited 數組,初始化為 -1,但是 (0, 0) 位置初始化為k,因為起始時可以去除障礙k次。接下來就是 BFS 的實現了,新建一個隊列 queue,然后把起始狀態位置加次數放到一個數組里,排入隊列中。
接下來進行 while 循環,注意是層序遍歷的寫法,里面用一個 for 循環,一次遍歷完當前層的所有元素。在 for 循環中,取出隊首元素,判斷當前位置是否為終點,是的話返回結果 res,否則就遍歷周圍四個位置。計算出新位置的坐標,若越界了則跳過,然后計算新位置的剩余去除障礙次數,由於新位置可能是障礙,可以直接減去對應位置的數值,因為障礙是1,減去1正好表示用了一次去除障礙,而通路是0,減去0則沒有變化。得到 newK 后,需要判斷一下,若其小於0了,說明去除障礙次數不夠用,需要跳過;或者 newK 小於等於 visited 數組的值,這種情況就是前面分析中說的需要剪枝的情況,同樣跳過。然后用 newK 來更新 visited 數組中的值,並把新的狀態加入到隊列中。每層遍歷結束后,記得結果 res 要自增1,參見代碼如下:
解法一:
class Solution {
public:
int shortestPath(vector<vector<int>>& grid, int k) {
int res = 0, m = grid.size(), n = grid[0].size();
vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
vector<vector<int>> visited(m, vector<int>(n, -1)); // The number of obstacles that we can still remove after walking through that cell
visited[0][0] = k;
queue<vector<int>> q;
q.push({0, 0, k});
while (!q.empty()) {
for (int i = q.size(); i > 0; --i) {
auto t = q.front(); q.pop();
if (t[0] == m - 1 && t[1] == n - 1) return res;
for (auto dir : dirs) {
int x = t[0] + dir[0], y = t[1] + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n) continue;
int newK = t[2] - grid[x][y];
if (newK < 0 || newK <= visited[x][y]) continue;
visited[x][y] = newK;
q.push({x, y, newK});
}
}
++res;
}
return -1;
}
};
或者我們也可以對 visitedd 數組的定義稍稍修改一下,反過來定義一下,表示到達該位置需要的移除的最少障礙數,則起始位置需要初始化0,其他位置則可以初始化為整型最大值。其余地方跟上面解法都很類似,不同點在於計算 newK,此時的 newK 應該是之前位置的剩余去除障礙次數加上新位置上的原數組中的值,然后判斷條件也是和上面翻過來,當 newK 大於k,或者 visited 中對應的值小於等於 newK 時,需要跳過。否則用 newK 來更新 visited 數組中的值,並把新的狀態加入到隊列中。每層遍歷結束后,記得結果 res 要自增1,參見代碼如下:
解法二:
class Solution {
public:
int shortestPath(vector<vector<int>>& grid, int k) {
int res = 0, m = grid.size(), n = grid[0].size();
vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
vector<vector<int>> visited(m, vector<int>(n, INT_MAX)); // Record the minimum obstacles removed to get to that position
visited[0][0] = 0;
queue<vector<int>> q;
q.push({0, 0, 0});
while (!q.empty()) {
for (int i = q.size(); i > 0; --i) {
auto t = q.front(); q.pop();
if (t[0] == m - 1 && t[1] == n - 1) return res;
for (auto dir : dirs) {
int x = t[0] + dir[0], y = t[1] + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n) continue;
int newK = t[2] + grid[x][y];
if (newK > k || visited[x][y] <= newK) continue;
visited[x][y] = newK;
q.push({x, y, newK});
}
}
++res;
}
return -1;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1293
類似題目:
Shortest Path to Get Food
參考資料:
https://leetcode.com/problems/shortest-path-in-a-grid-with-obstacles-elimination/