Starting with an undirected graph (the "original graph") with nodes from 0
to N-1
, subdivisions are made to some of the edges.html
The graph is given as follows: edges[k]
is a list of integer pairs (i, j, n)
such that (i, j)
is an edge of the original graph,node
and n
is the total number of new nodes on that edge. git
Then, the edge (i, j)
is deleted from the original graph, n
new nodes (x_1, x_2, ..., x_n)
are added to the original graph,github
and n+1
new edges (i, x_1), (x_1, x_2), (x_2, x_3), ..., (x_{n-1}, x_n), (x_n, j)
are added to the original graph.算法
Now, you start at node 0
from the original graph, and in each move, you travel along one edge. 數組
Return how many nodes you can reach in at most M
moves.ide
Example 1:code
Input: `edges` = [[0,1,10],[0,2,1],[1,2,2]], M = 6, N = 3 Output: 13 Explanation: The nodes that are reachable in the final graph after M = 6 moves are indicated below.
Example 2:htm
Input: `edges` = [[0,1,4],[1,2,6],[0,2,8],[1,3,1]], M = 10, N = 4 Output: 23
Note:blog
0 <= edges.length <= 10000
0 <= edges[i][0] < edges[i][1] < N
i != j
for which edges[i][0] == edges[j][0]
and edges[i][1] == edges[j][1]
.0 <= edges[i][2] <= 10000
0 <= M <= 10^9
1 <= N <= 3000
這道題給了咱們一個無向圖,裏面有N個結點,可是每兩個結點中間可能有多個不一樣的結點,假設每到達下一個相鄰的結點須要消耗一步,如今咱們有M步能夠走,問咱們在M步內最多能夠到達多少個不一樣的結點。這裏雖然有N個有編號的大結點,中間還有若干個沒有編號的小結點,可是最後在統計的時候不分大小結點,全都算不一樣的結點。爲了更好的理解這道題,實際上能夠把N個有編號的結點看成N個大城市,好比省會城市,每兩個省會城市中間有多個小城市,假設咱們每次坐飛機只能飛到相鄰的下一個城市,如今咱們最多能坐M次飛機,問從省會大城市0出發的話,最多能到達多少個城市。因爲省會城市是大型中轉站,因此只有在這裏纔能有多個選擇去往不一樣的城市,而在兩個省會城市中的每一個小城市,只有先後兩種選擇,因此這道題實際上仍是一種圖的遍歷,只不過不保證每次都能到有編號的結點,只有到達了有編號的結點,才能夠繼續遍歷下去。當到達了有編號的結點時,還要計算此時的剩餘步數,就是用前一個有編號結點的剩餘步數,減去當前路徑上的全部小結點的個數。假如當前的剩餘步數不夠到達下一個大結點時,此時咱們要想辦法標記出來咱們走過了多少個小結點,否則下次咱們經過另外一條路徑到達相同的下一個大結點時,再往回走就有可能重複統計小結點的個數。因爲小結點並無標號,無法直接標記,只能經過離最近的大結點的個數來標記,因此雖然這道題是一道無向圖的題,可是咱們須要將其看成有向圖來處理,好比兩個大結點A和B,中間有10個小結點,此時在A結點時只有6步能走,那麼咱們走了中間的6個結點,此時就要標記從B出發往A方向的話只有4個小結點能走了。
再進一步來分析,其實上對於每一個結點來講(不論有沒有編號),若咱們能算出該結點離起始結點的最短距離,且該距離小於等於M的話,那這個結點就必定能夠到達。這樣來講,其實本質就是求單源點的最短距離,此時就要祭出神器迪傑斯特拉算法 Dijkstra Algorithm 了,LeetCode 中使用了該算法的題目還有 Network Delay Time 和 The Maze II。該算法的通常形式是用一個最小堆來保存到源點的最小距離,這裏咱們直接統計到源點的最小距離不是很方便,可使用一個小 trick,即用一個最大堆來統計當前結點所剩的最大步數,由於剩的步數越多,說明距離源點距離越小。因爲 Dijkstra 算法是以起點爲中心,向外層層擴展,直到擴展到終點爲止。根據這特性,用 BFS 來實現時再好不過了,首先來創建鄰接鏈表,這裏可使用一個 NxN 的二維數組 graph,其中 graph[i][j] 表示從大結點i往大結點j方向會通過的小結點個數,創建鄰接鏈表的時候對於每一個 edge,要把兩個方向都賦值,前面解釋過了這裏要看成有向圖來作。而後使用一個最大堆,裏面放剩餘步數和結點編號組成的數對兒,把剩餘步數放前面就能夠默認按步數從大到小排序了,初始化時把 {M,0} 存入最大堆。還須要一個一維數組 visited 來記錄某個結點是否訪問過。在 while 循環中,首先取出堆頂元素數對兒,分別取出步數 move,和當前結點編號 cur,此時檢查若該結點已經訪問過了,直接跳過,不然就在 visited 數組中標記爲 true。此時結果 res 自增1,由於當前大結點也是新遍歷到的,須要累加個數。而後咱們須要遍歷全部跟 cur 相連的大結點,對於二維數組形式的鄰接鏈表,咱們只須要將i從0遍歷到N,假如 graph[cur][i] 爲 -1,表示結點 cur 和結點i不相連,直接跳過。不然相連的話,兩個大結點中小結點的個數爲 graph[cur][i],此時要跟當前 cur 結點時剩餘步數 move 比較,假如 move 較大,說明能夠到達結點i,將此時到達結點i的剩餘步數 move-graph[cur][i]-1(最後的減1是到達結點i須要的額外步數)和i一塊兒組成數對兒,加入最大堆中。因爲以前的分析,結點 cur 往結點i走過的全部結點,從結點i就不能再往結點 cur 走了,不然就累加了重複結點,因此 graph[i][cur] 要減去 move 和 graph[cur][i] 中的較小值,同時結果 res 要累加該較小值便可,參見代碼以下:
解法一:
class Solution { public: int reachableNodes(vector<vector<int>>& edges, int M, int N) { int res = 0; vector<vector<int>> graph(N, vector<int>(N, -1)); vector<bool> visited(N); priority_queue<pair<int, int>> pq; pq.push({M, 0}); for (auto &edge : edges) { graph[edge[0]][edge[1]] = edge[2]; graph[edge[1]][edge[0]] = edge[2]; } while (!pq.empty()) { auto t= pq.top(); pq.pop(); int move = t.first, cur = t.second; if (visited[cur]) continue; visited[cur] = true; ++res; for (int i = 0; i < N; ++i) { if (graph[cur][i] == -1) continue; if (move > graph[cur][i] && !visited[i]) { pq.push({move - graph[cur][i] - 1, i}); } graph[i][cur] -= min(move, graph[cur][i]); res += min(move, graph[cur][i]); } } return res; } };
咱們也可使用 HashMap 來創建鄰接鏈表,最後的運行速度果真要比二維數組形式的鄰接鏈表要快一些,其餘的地方都不變,參見代碼以下:
解法二:
class Solution { public: int reachableNodes(vector<vector<int>>& edges, int M, int N) { int res = 0; unordered_map<int, unordered_map<int, int>> graph; vector<bool> visited(N); priority_queue<pair<int, int>> pq; pq.push({M, 0}); for (auto &edge : edges) { graph[edge[0]][edge[1]] = edge[2]; graph[edge[1]][edge[0]] = edge[2]; } while (!pq.empty()) { auto t= pq.top(); pq.pop(); int move = t.first, cur = t.second; if (visited[cur]) continue; visited[cur] = true; ++res; for (auto &a : graph[cur]) { if (move > a.second && !visited[a.first]) { pq.push({move - a.second - 1, a.first}); } graph[a.first][cur] -= min(move, a.second); res += min(move, a.second); } } return res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/882
參考資料:
https://leetcode.com/problems/reachable-nodes-in-subdivided-graph/