[LeetCode] Swim in Rising Water 在上升的水中游泳
On an N x N grid
, each square grid[i][j]
represents the elevation at that point (i,j)
.
Now rain starts to fall. At time t
, the depth of the water everywhere is t
. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most t
You start at the top left square (0, 0)
. What is the least time until you can reach the bottom right square (N-1, N-1)
?
Example 1:
Input: [[0,2],[1,3]] Output: 3 Explanation: At time0
, you are in grid location(0, 0)
. You cannot go anywhere else because 4-directionally adjacent neighbors have a higher elevation than t = 0. You cannot reach point(1, 1)
until time3
. When the depth of water is3
, we can swim anywhere inside the grid.
Example 2:
Input: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]] Output: 16 Explanation: 0 1 2 3 424 23 22 21 5 12 13 14 15 16 11 17 18 19 20 10 9 8 7 6 The final route is marked in bold. We need to wait until time 16 so that (0, 0) and (4, 4) are connected.
Note:
2 <= N <= 50
.- grid[i][j] is a permutation of [0, ..., N*N - 1].
這道題給了我們一個二維陣列,可以看作一個水池,這裡不同數字的高度可以看作臺階的高度,只有當水面升高到臺階的高度時,我們才能到達該臺階,起始點在左上角位置,問我們水面最低升到啥高度就可以到達右下角的位置。這是一道蠻有意思的題目。對於這種類似迷宮遍歷的題,一般都是DFS或者BFS。而如果有極值問題存在的時候,一般都是優先考慮BFS的,但是這道題比較特別,有一個上升水面的設定,我們可以想象一下,比如洪水爆發了,大壩垮了,那麼憤怒洶湧的水流衝了出來,地勢低窪處就會被淹沒,而地勢高的地方,比如山峰啥的,就會繞道而過。這裡也是一樣,隨著水面不斷的上升,低於水平面的地方就可以到達,直到水流到了右下角的位置停止。因為水流要向周圍低窪處蔓延,所以BFS仍是一個不錯的選擇,由於水是向低窪處蔓延的,而低窪處的位置又是不定的,所以我們希望每次取出最低位置進行遍歷,那麼使用最小堆就是一個很好的選擇,這樣高度低的就會被先處理。在每次取出高度最小的數字時,我們用此高度來更新結果res,如果當前位置已經是右下角了,則我們直接返回結果res,否則就遍歷當前位置的周圍位置,如果未越界且未被訪問過,則標記已經訪問過,並且加入佇列,參見程式碼如下:
解法一:
class Solution { public: int swimInWater(vector<vector<int>>& grid) { int res = 0, n = grid.size(); unordered_set<int> visited{0}; vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; auto cmp = [](pair<int, int>& a, pair<int, int>& b) {return a.first > b.first;}; priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp) > q(cmp); q.push({grid[0][0], 0}); while (!q.empty()) { int i = q.top().second / n, j = q.top().second % n; q.pop(); res = max(res, grid[i][j]); if (i == n - 1 && j == n - 1) return res; for (auto dir : dirs) { int x = i + dir[0], y = j + dir[1]; if (x < 0 || x >= n || y < 0 || y >= n || visited.count(x * n + y)) continue; visited.insert(x * n + y); q.push({grid[x][y], x * n + y}); } } return res; } };
我們也可以使用DP+DFS來做,這裡使用一個二維dp陣列,其中 dp[i][j] 表示到達 (i, j) 位置所需要的最低水面高度,均初始化為整型數最大值,我們的遞迴函式函式需要知道當前的位置 (x, y),還有當前的水高cur,同時傳入grid陣列和需要不停更新的dp陣列,如果當前位置越界了,或者是當前水高和 grid[x][y] 中的較大值大於等於 dp[x][y] 了,直接跳過,因為此時的dp值更小,不需要被更新了。否則 dp[x][y] 更新為較大值,然後對周圍四個位置呼叫遞迴函式繼續更新dp陣列,最終返回右下位置的dp值即可,參見程式碼如下:
解法二:
class Solution { public: vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; int swimInWater(vector<vector<int>>& grid) { int n = grid.size(); vector<vector<int>> dp(n, vector<int>(n, INT_MAX)); helper(grid, 0, 0, grid[0][0], dp); return dp[n - 1][n - 1]; } void helper(vector<vector<int>>& grid, int x, int y, int cur, vector<vector<int>>& dp) { int n = grid.size(); if (x < 0 || x >= n || y < 0 || y >= n || max(cur, grid[x][y]) >= dp[x][y]) return; dp[x][y] = max(cur, grid[x][y]); for (auto dir : dirs) { helper(grid, x + dir[0], y + dir[1], dp[x][y], dp); } } };
其實這道題還可以使用二分搜尋法來做,屬於博主的總結帖中LeetCode Binary Search Summary 二分搜尋法小結的第四類,用子函式當作判斷關係。由於題目中給定了數字的範圍,那麼二分搜尋法的左右邊界就有了,然後我們計算一箇中間值mid,呼叫子函式來看這個水面高度下能否到達右下角,如果不能的話,說明水面高度不夠,則 left = mid+1,如果能到達的話,有可能水面高度過高了,則right = mid,最終會到達的臨界點就是能到達右下角的最低水面高度。那麼來看子函式怎麼寫,其實就是個迷宮遍歷問題,我們可以使用BFS或者DFS,這裡使用了stack輔助的迭代形式的DFS來遍歷,當然我們也可以使用queue輔助的迭代形式的BFS來遍歷,都一樣,如果在mid的水面高度下,遍歷到了右下角,則返回true,否則返回false,參見程式碼如下:
解法三:
class Solution { public: int swimInWater(vector<vector<int>>& grid) { int n = grid.size(); int left = grid[0][0], right = n * n; while (left < right) { int mid = left + (right - left) / 2; if (!helper(grid, mid)) left = mid + 1; else right = mid; } return left; } bool helper(vector<vector<int>>& grid, int mid) { int n = grid.size(); unordered_set<int> visited{0}; vector<vector<int>> dirs{{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; stack<int> st{{0}}; while (!st.empty()) { int i = st.top() / n, j = st.top() % n; st.pop(); if (i == n - 1 && j == n - 1) return true; for (auto dir : dirs) { int x = i + dir[0], y = j + dir[1]; if (x < 0 || x >= n || y < 0 || y >= n || visited.count(x * n + y) || grid[x][y] > mid) continue; st.push(x * n + y); visited.insert(x * n + y); } } return false; } };
參考資料: