1. 程式人生 > >[LeetCode] Swim in Rising Water 在上升的水中游泳

[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 can swim infinite distance in zero time. Of course, you must stay within the boundaries of the grid during your swim.

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 time 0
, 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 time 3. When the depth of water is 3, 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  4
24 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:

  1. 2 <= N <= 50.
  2. 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;
    }
};

參考資料: