We have a list of bus routes. Each routes[i]
is a bus route that the i-th bus repeats forever. For example if routes[0] = [1, 5, 7]
, this means that the first bus (0-th indexed) travels in the sequence 1->5->7->1->5->7->1->... forever.
We start at bus stop S
(initially not on a bus), and we want to go to bus stop T
. Travelling by buses only, what is the least number of buses we must take to reach our destination? Return -1 if it is not possible.
Example: Input: routes = [[1, 2, 7], [3, 6, 7]] S = 1 T = 6 Output: 2 Explanation: The best strategy is take the first bus to the bus stop 7, then take the second bus to the bus stop 6.
Note:
1 <= routes.length <= 500
.1 <= routes[i].length <= 500
.0 <= routes[i][j] < 10 ^ 6
.
這道題給了我們一堆公交線路表,然后給了起點和終點,問最少要換乘幾輛公交可以從起點到達終點。這種原本只需要使用谷歌地圖或者百度地圖輕松實現的事,現在需要自己來實現。但這畢竟是簡化版,真實情況一定要復雜得多。 這題容易進的一個誤區就是把 routes 直接當作鄰接鏈表來進行圖的遍歷,其實是不對的,因為 routes 數組的含義是,某個公交所能到達的站點,而不是某個站點所能到達的其他站點。這里出現了兩種不同的結點,分別是站點和公交。而 routes 數組建立的是公交和其站點之間的關系,那么應該將反向關系數組也建立出來,即要知道每個站點有哪些公交可以到達。由於這里站點的標號不一定是連續的,所以可以使用 HashMap,建立每個站點和其屬於的公交數組之間的映射。由於一個站點可以被多個公交使用,所以要用個數組來保存公交。既然這里求的是最少使用公交的數量,那么就類似迷宮遍歷求最短路徑的問題,BFS 應該是首先被考慮的解法。用隊列 queue 來輔助,首先將起點S排入隊列中,然后還需要一個 HashSet 來保存已經遍歷過的公交(注意這里思考一下,為啥放的是公交而不是站點,因為統計的是最少需要坐的公交個數,這里一層就相當於一輛公交,最小的層數就是公交數),這些都是 BFS 的標配,應當已經很熟練了。在最開頭先判斷一下,若起點和終點相同,那么直接返回0,因為根本不用坐公交。否則開始 while 循環,先將結果 res 自增1,因為既然已經上了公交,那么公交個數至少為1,初始化的時候是0。這里使用 BFS 的層序遍歷的寫法,就是當前所有的結點都當作深度相同的一層,至於為何采用這種倒序遍歷的 for 循環寫法,是因為之后隊列的大小可能變化,放在判斷條件中可能會出錯。在 for 循環中,先取出隊首站點,然后要去 HashMap 中去遍歷經過該站點的所有公交,若某個公交已經遍歷過了,直接跳過,否則就加入 visited 中。然后去 routes 數組中取出該公交的所有站點,如果有終點,則直接返回結果 res,否則就將站點排入隊列中繼續遍歷,參見代碼如下:
解法一:
class Solution { public: int numBusesToDestination(vector<vector<int>>& routes, int S, int T) { if (S == T) return 0; int res = 0; unordered_map<int, vector<int>> stop2bus; queue<int> q{{S}}; unordered_set<int> visited; for (int i = 0; i < routes.size(); ++i) { for (int j : routes[i]) { stop2bus[j].push_back(i); } } while (!q.empty()) { ++res; for (int i = q.size(); i > 0; --i) { int t = q.front(); q.pop(); for (int bus : stop2bus[t]) { if (visited.count(bus)) continue; visited.insert(bus); for (int stop : routes[bus]) { if (stop == T) return res; q.push(stop); } } } } return -1; } };
下面這種方法也是 BFS 解法,思路上跟上面的解法沒有啥大的區別,就是數據結構的寫法上略有不同。這里的隊列 queue 放的是一個由站點和公交個數組成的 pair 對兒,這樣就不用維護一個全局的最小公交數變量了。當然反向關系數組還是要建立出來的,即要知道每個站點有哪些公交可以到達。和上面稍有不同的是,使用了 HashSet 來保存經過某個站點的所有公交,但其實和用數組並沒啥區別,因為這里沒有查詢需求,無法發揮 HashSet 的優勢。由於對於每個站點,都保存了當達該站點所需的最少公交數,那么就不需要使用層序遍歷的 BFS 的寫法,直接用最一般的寫法即可。還有一個不同之處在於,這里的 visited 保存的是遍歷過的站點,而不再是公交了。在 while 循環中,首先將隊首元素取出來,這里就取出來了當前站點 cur,和最少公交數 cnt,若當前站點就是終點,那就直接返回 cnt。否則遍歷經過當前站點的所有公交,對每輛公交,再去遍歷去所有站點,若站點已經被遍歷過了,直接跳過,否則就加入 visited 中,並和 cnt+1 一起組成個 pair 對兒排入隊列中繼續遍歷,參見代碼如下:
解法二:
class Solution { public: int numBusesToDestination(vector<vector<int>>& routes, int S, int T) { if (S == T) return 0; unordered_map<int, unordered_set<int>> stop2bus; queue<pair<int, int>> q{{{S, 0}}}; unordered_set<int> visited; for (int i = 0; i < routes.size(); ++i) { for (int j : routes[i]) { stop2bus[j].insert(i); } } while (!q.empty()) { int cur = q.front().first, cnt = q.front().second; q.pop(); if (cur == T) return cnt; for (int bus : stop2bus[cur]) { for (int stop : routes[bus]) { if (visited.count(stop)) continue; visited.insert(stop); q.push({stop, cnt + 1}); } } } return -1; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/815
參考資料:
https://leetcode.com/problems/bus-routes/
https://leetcode.com/problems/bus-routes/discuss/122712/Simple-Java-Solution-using-BFS
https://leetcode.com/problems/bus-routes/discuss/122771/C%2B%2BJavaPython-BFS-Solution