1. 程式人生 > >[LeetCode] Shortest Distance from All Buildings 建築物的最短距離

[LeetCode] Shortest Distance from All Buildings 建築物的最短距離

You want to build a house on an empty land which reaches all buildings in the shortest amount of distance. You can only move up, down, left and right. You are given a 2D grid of values 0, 1 or 2, where:

  • Each 0 marks an empty land which you can pass by freely.
  • Each 1 marks a building which you cannot pass through.
  • Each 2 marks an obstacle which you cannot pass through.

For example, given three buildings at (0,0)(0,4)(2,2), and an obstacle at (0,2):

1 - 0 - 2 - 0 - 1
|   |   |   |   |
0 - 0 - 0 - 0 - 0
|   |   |   |   |
0 - 0 - 1 - 0 - 0

The point (1,2) is an ideal empty land to build a house, as the total travel distance of 3+3+1=7 is minimal. So return 7.

Note:
There will be at least one building. If it is not possible to build such house according to the above rules, return -1.

這道題給我們了一些建築物的座標和一些障礙物的座標,讓我們找一個位置,使其到所有建築物的曼哈頓距離之和最小,起初我覺得這題應該算Best Meeting Point那道題的拓展,不同之處在於這道題有了障礙物的存在,這樣就使得直接使用曼哈頓距離的計算公式變得不可行,因為在有些情況下,障礙物完全封死了某個建築物,那麼這時候應該返回-1。所以這道題只能使用遍歷迷宮的思想來解,那麼這題就和之前那道
Walls and Gates
很類似,但是這道題用DFS就會很麻煩,因為我們的目標是要建立Distance Map,所以BFS的特性使得其非常適合建立距離場,而DFS由於是沿著一個方向一股腦的搜尋,然後會面臨著更新距離的問題,只有當遞迴函式都呼叫結束後,距離場才建立好,那麼我們累加距離場時又得整個遍歷一遍,非常不高效。主要原因還是由於DFS的搜尋方式不適合距離場,因為BFS遍歷完一個點後,不會再來更改這個點的值,而DFS會反覆的更改同一個點的值,我強行用DFS寫出的方法無法通過OJ最後一個大集合,所以這道題還是老老實實地用BFS來解題吧,還是需要藉助queue來遍歷,我們對於每一個建築的位置都進行一次全圖的BFS遍歷,每次都建立一個dist的距離場,由於我們BFS遍歷需要標記應經訪問過的位置,而我們並不想建立一個visit的二維矩陣,那麼怎麼辦呢,這裡用一個小trick,我們第一遍歷的時候,都是找0的位置,遍歷完後,我們將其賦為-1,這樣下一輪遍歷我們就找-1的位置,然後將其都賦為-2,以此類推直至遍歷完所有的建築物,然後在遍歷的過程中更新dist和sum的值,注意我們的dist算是個區域性變數,每次都初始化為grid,真正的距離場累加在sum中,由於建築的位置在grid中是1,所以dist中初始化也是1,累加到sum中就需要減1,我們用sum中的值來更新結果res的值,最後根據res的值看是否要返回-1,參見程式碼如下:

解法一:

class Solution {
public:
    int shortestDistance(vector<vector<int>>& grid) {
        int res = INT_MAX, val = 0, m = grid.size(), n = grid[0].size();
        vector<vector<int>> sum = grid;
        vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}};
        for (int i = 0; i < grid.size(); ++i) {
            for (int j = 0; j < grid[i].size(); ++j) {
                if (grid[i][j] == 1) {
                    res = INT_MAX;
                    vector<vector<int>> dist = grid;
                    queue<pair<int, int>> q;
                    q.push({i, j});
                    while (!q.empty()) {
                        int a = q.front().first, b = q.front().second; q.pop();
                        for (int k = 0; k < dirs.size(); ++k) {
                            int x = a + dirs[k][0], y = b + dirs[k][1];
                            if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == val) {
                                --grid[x][y];
                                dist[x][y] = dist[a][b] + 1;
                                sum[x][y] += dist[x][y] - 1;
                                q.push({x, y});
                                res = min(res, sum[x][y]);
                            }
                        }
                    }
                    --val;                    
                }
            }
        }
        return res == INT_MAX ? -1 : res;
    }
};

下面這種方法也是網上比較流行的解法,我們還是用BFS來做,其中dist是累加距離場,cnt表示某個位置已經計算過的建築數,變數buildingCnt為建築的總數,我們還是用queue來輔助計算,注意這裡的dist的更新方式跟上面那種方法的不同,這裡的dist由於是累積距離場,所以不能用dist其他位置的值來更新,而是需要直接加上和建築物之間的距離,這裡用level來表示,每遍歷一層,level自增1,這樣我們就需要所加個for迴圈,來控制每一層中的level值是相等的,參見程式碼如下:

解法二:

class Solution {
public:
    int shortestDistance(vector<vector<int>>& grid) {
        int res = INT_MAX, buildingCnt = 0, m = grid.size(), n = grid[0].size();
        vector<vector<int>> dist(m, vector<int>(n, 0)), cnt = dist;
        vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}};
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    ++buildingCnt;
                    queue<pair<int, int>> q;
                    q.push({i, j});
                    vector<vector<bool>> visited(m, vector<bool>(n, false));
                    int level = 1;
                    while (!q.empty()) {
                        int size = q.size();
                        for (int s = 0; s < size; ++s) {
                            int a = q.front().first, b = q.front().second; q.pop();
                            for (int k = 0; k < dirs.size(); ++k) {
                                int x = a + dirs[k][0], y = b + dirs[k][1];
                                if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 0 && !visited[x][y]) {
                                    dist[x][y] += level;
                                    ++cnt[x][y];
                                    visited[x][y] = true;
                                    q.push({x, y});
                                }
                            }
                        }
                        ++level;
                    }
                }
            }
        }
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 0 && cnt[i][j] == buildingCnt) {
                    res = min(res, dist[i][j]);
                }
            }
        }
        return res == INT_MAX ? -1 : res;
    }
};

類似題目:

參考資料: