力扣 leetcode 882. 细分图中的可到达节点

问题描述

给你一个无向图(原始图),图中有 n 个节点,编号从 0n - 1 。你决定将图中的每条边 细分 为一条节点链,每条边之间的新节点数各不相同。

图用由边组成的二维数组 edges 表示,其中 edges[i] = [ui, vi, cnti] 表示原始图中节点 uivi 之间存在一条边,cnti 是将边 细分 后的新节点总数。注意,cnti == 0 表示边不可细分。

细分[ui, vi] ,需要将其替换为 (cnti + 1) 条新边,和 cnti 个新节点。新节点为 x1, x2, ..., xcnti ,新边为 [ui, x1], [x1, x2], [x2, x3], ..., [xcnti+1, xcnti], [xcnti, vi]

现在得到一个 新的细分图 ,请你计算从节点 0 出发,可以到达多少个节点?如果节点间距离是 maxMoves 或更少,则视为 可以到达

给你原始图和 maxMoves返回 新的细分图中从节点 0 出发 可到达的节点数

提示:

  • 0 <= edges.length <= min(n * (n - 1) / 2, 104)
  • edges[i].length == 3
  • 0 <= ui < vi < n
  • 图中 不存在平行边
  • 0 <= cnti <= 104
  • 0 <= maxMoves <= 109
  • 1 <= n <= 3000

示例:

示例1:

输入:edges = [[0,1,10],[0,2,1],[1,2,2]], maxMoves = 6, n = 3
输出:13
解释:边的细分情况如上图所示。
可以到达的节点已经用黄色标注出来。

示例2:

输入:edges = [[0,1,4],[1,2,6],[0,2,8],[1,3,1]], maxMoves = 10, n = 4
输出:23

示例3:

输入:edges = [[1,2,4],[1,4,5],[1,3,1],[2,3,4],[3,4,5]], maxMoves = 17, n = 5
输出:1
解释:节点 0 与图的其余部分没有连通,所以只有节点 0 可以到达。

解题思路

这个题目考察的主要是图的遍历以及最短路径的求解。如果直接先对图进行遍历,然后计算可达的节点,则存在很多复杂的情况需要考虑。产生这些情况的原因和计算最短路径长度时是一致的,每当更新过一次最短距离后,该节点相邻节点的最短距离也需要更新。如果直接判断这些情况,则会造成代码太过冗长,而且也效率也不高。比较好的策略是先计算每个节点到节点0的最短距离。

首先,图的遍历方法主要有广度优先搜索(BFS)和深度优先搜索(DFS)两种,在本题中,这两种方法都可。接着我们可以将问题转化为,判断从节点0出发,是否可以抵达节点n,并计算节点0到达节点n的最短距离。值得注意的是,这里限制了节点0与其它节点的最大长度。

常用的计算指定节点到达其它节点的最短距离的算法是迪杰斯特拉算法(Dijkstra),Dijkstra的最大缺点是当边的权值为负时无法计算,但在本题中不受影响。

假设我们已经找到了节点0到达其它节点的最短距离,我们只需再对图进行一次遍历,并计算每条可达边以及不可达边的长度。代码如下:

class Solution {
public:
    int reachableNodes(vector<vector<int>>& edges, int maxMoves, int n) {
        int node;
        int dis;
        int cnt = 1;
        // 记录当前遍历的节点,用队列实现广度优先搜索
        queue<int> nodes;
        vector<bool> unvisited(n, true);
        // 保存每个节点到节点0的最短距离
        vector<int> distance(n, 0x7fffffff);
        // 保存每个节点的边所在位置
        vector<vector<int>> matrix(n, vector<int>());
        nodes.push(0);
        distance[0] = 0;
        int u, v, d;
        for(int i = 0; i < edges.size(); i++){
            // 记录每个节点的边在edges中的下标
            matrix[edges[i][0]].push_back(i);
            matrix[edges[i][1]].push_back(i);
        }
        while(nodes.size() > 0){
            node = nodes.front();
            nodes.pop();
            for(int i = 0; i < matrix[node].size(); i++){
                u = edges[matrix[node][i]][0];
                v = edges[matrix[node][i]][1];
                d = edges[matrix[node][i]][2];
                if(u == node){ // 判断是否要更新距离
                    if(distance[v] > (d + distance[u] + 1)){
                        distance[v] = d + distance[u] + 1;
                        nodes.push(v); // 更新距离,并将更新后的节点添加到队列中
                    }
                }
                else if(v == node){
                    if(distance[u] > (d + distance[v] + 1)){
                        distance[u] = d + distance[v] + 1;
                        nodes.push(u);
                    }
                }
            }
        }
        int tmp;
        unvisited[0] = false;
        for(int i = 0; i < edges.size(); i++){
            u = edges[i][0];
            v = edges[i][1];
            dis = distance[u] < distance[v] ? distance[u] : distance[v]; 
            if(dis > maxMoves){ // 判断节点是否可达,如果dis > maxMoves,说明两个节点都不可达
                continue;
            }
            if(dis + edges[i][2] < maxMoves){ // 判断两个节点间的新增节点是否能够完全遍历
                cnt += edges[i][2] + unvisited[u] + unvisited[v];
                unvisited[u] = false;
                unvisited[v] = false;
            }
            else{ // 两个节点间的节点无法完全遍历
                tmp = 0;
                if(distance[u] <= maxMoves){ // 从节点u开始遍历,计算可以遍历uv间的几个节点
                    tmp = maxMoves - distance[u];
                    cnt += tmp + unvisited[u];
                    unvisited[u] = false;
                }
                if(distance[v] <= maxMoves){ // 注意,此时从u节点已经遍历过一部分节点了,为了避免重复遍历,必须判断uv间节点个数
                    dis = maxMoves - distance[v];
                    tmp = edges[i][2] - tmp;
                    // 如果uv间节点个数足够,则全部添加,否则要减去之前遍历的部分
                    cnt +=  tmp > dis ? (dis + unvisited[v]) : (tmp + unvisited[v]);
                    unvisited[v] = false;
                }
            }
        }
        return cnt;
    }
};

在上述代码中,我们先计算了每个节点到节点0的最短距离,然后再判断给出的每条边中可以遍历多少节点,最后再统计可以遍历的节点个数。值得注意的是,这里最好是直接根据传入的边来统计,不建议使用邻接表,否则很容易超时。

posted @ 2022-11-26 21:37  greatestchen  阅读(16)  评论(0编辑  收藏  举报