[LeetCode] 882. Reachable Nodes In Subdivided Graph 細分圖中的可到達結點



Starting with an undirected graph (the "original graph") with nodes from `0` to `N-1`, subdivisions are made to some of the edges.

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,

and n is the total number of new nodes on that edge.

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,

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 Mmoves.

Example 1:

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:

Input: `edges` = [[0,1,4],[1,2,6],[0,2,8],[1,3,1]], M = 10, N = 4
Output: 23

Note:

  1. 0 <= edges.length <= 10000
  2. 0 <= edges[i][0] < edges[i][1] < N
  3. There does not exist any i != j for which edges[i][0] == edges[j][0] and edges[i][1] == edges[j][1].
  4. The original graph has no parallel edges.
  5. 0 <= edges[i][2] <= 10000
  6. 0 <= M <= 10^9
  7. 1 <= N <= 3000
  8. A reachable node is a node that can be travelled to using at most M moves starting from node 0.

這道題給了我們一個無向圖,里面有N個結點,但是每兩個結點中間可能有多個不同的結點,假設每到達下一個相鄰的結點需要消耗一步,現在我們有M步可以走,問我們在M步內最多可以到達多少個不同的結點。這里雖然有N個有編號的大結點,中間還有若干個沒有編號的小結點,但是最后在統計的時候不分大小結點,全都算不同的結點。為了更好的理解這道題,實際上可以把N個有編號的結點當作N個大城市,比如省會城市,每兩個省會城市中間有多個小城市,假設我們每次坐飛機只能飛到相鄰的下一個城市,現在我們最多能坐M次飛機,問從省會大城市0出發的話,最多能到達多少個城市。由於省會城市是大型中轉站,所以只有在這里才能有多個選擇去往不同的城市,而在兩個省會城市中的每個小城市,只有前后兩種選擇,所以這道題實際上還是一種圖的遍歷,只不過不保證每次都能到有編號的結點,只有到達了有編號的結點,才可以繼續遍歷下去。當到達了有編號的結點時,還要計算此時的剩余步數,就是用前一個有編號結點的剩余步數,減去當前路徑上的所有小結點的個數。假如當前的剩余步數不夠到達下一個大結點時,此時我們要想辦法標記出來我們走過了多少個小結點,不然下次我們通過另一條路徑到達相同的下一個大結點時,再往回走就有可能重復統計小結點的個數。由於小結點並沒有標號,沒法直接標記,只能通過離最近的大結點的個數來標記,所以雖然這道題是一道無向圖的題,但是我們需要將其當作有向圖來處理,比如兩個大結點A和B,中間有10個小結點,此時在A結點時只有6步能走,那么我們走了中間的6個結點,此時就要標記從B出發往A方向的話只有4個小結點能走了。

再進一步來分析,其實上對於每個結點來說(不論有沒有編號),若我們能算出該結點離起始結點的最短距離,且該距離小於等於M的話,那這個結點就一定可以到達。這樣來說,其實本質就是求單源點的最短距離,此時就要祭出神器迪傑斯特拉算法 Dijkstra Algorithm 了,LeetCode 中使用了該算法的題目還有 Network Delay TimeThe 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/

https://leetcode.com/problems/reachable-nodes-in-subdivided-graph/discuss/156777/Java-Dijkstra-Solution

https://leetcode.com/problems/reachable-nodes-in-subdivided-graph/discuss/156739/C%2B%2BJavaPython-Dijkstra-%2B-Priority-Queue


[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM