There are N network nodes, labelled 1 to N.

Given times, a list of travel times as directededges times[i] = (u, v, w), where u is the source node, v is the target node, and w is the time it takes for a signal to travel from source to target.

Now, we send a signal from a certain node K. How long will it take for all nodes to receive the signal? If it is impossible, return -1.

Example 1:

Input: times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2
Output: 2

Note:

  1. N will be in the range [1, 100].
  2. K will be in the range [1, N].
  3. The length of times will be in the range [1, 6000].
  4. All edges times[i] = (u, v, w) will have 1 <= u, v <= N and 0 <= w <= 100.

这道题给了我们一些有向边,又给了一个结点K,问至少需要多少时间才能从K到达任何一个结点。这实际上是一个有向图求最短路径的问题,求出K点到每一个点到最短路径,然后取其中最大的一个就是需要的时间了。可以想成从结点K开始有水流向周围扩散,当水流到达最远的一个结点时,那么其他所有的结点一定已经流过水了。最短路径的常用解法有迪杰斯特拉算法 Dijkstra Algorithm, 弗洛伊德算法 Floyd-Warshall Algorithm, 和贝尔曼福特算法 Bellman-Ford Algorithm,其中,Floyd 算法是多源最短路径,即求任意点到任意点到最短路径,而 Dijkstra 算法和 Bellman-Ford 算法是单源最短路径,即单个点到任意点到最短路径。这里因为起点只有一个K,所以使用单源最短路径就行了。这三种算法还有一点不同,就是 Dijkstra 算法处理有向权重图时,权重必须为正,而另外两种可以处理负权重有向图,但是不能出现负环,所谓负环,就是权重均为负的环。为啥呢,这里要先引入松弛操作 Relaxtion,这是这三个算法的核心思想,当有对边 (u, v) 是结点u到结点v,如果 dist(v) > dist(u) + w(u, v),那么 dist(v) 就可以被更新,这是所有这些的算法的核心操作。Dijkstra 算法是以起点为中心,向外层层扩展,直到扩展到终点为止。根据这特性,用 BFS 来实现时再好不过了,注意 while 循环里的第一层 for 循环,这保证了每一层的结点先被处理完,才会进入进入下一层,这种特性在用 BFS 遍历迷宫统计步数的时候很重要。对于每一个结点,都跟其周围的结点进行 Relaxtion 操作,从而更新周围结点的距离值。为了防止重复比较,需要使用 visited 数组来记录已访问过的结点,最后在所有的最小路径中选最大的返回,注意,如果结果 res 为 INT_MAX,说明有些结点是无法到达的,返回 -1。普通的实现方法的时间复杂度为 O(V2),基于优先队列的实现方法的时间复杂度为 O(E + VlogV),其中V和E分别为结点和边的个数,这里多说一句,Dijkstra 算法这种类贪心算法的机制,使得其无法处理有负权重的最短距离,还好这道题的权重都是正数,参见代码如下:

解法一:

class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
int res = ;
vector<vector<int>> edges(, vector<int>(, -));
queue<int> q{{K}};
vector<int> dist(N + , INT_MAX);
dist[K] = ;
for (auto e : times) edges[e[]][e[]] = e[];
while (!q.empty()) {
unordered_set<int> visited;
for (int i = q.size(); i > ; --i) {
int u = q.front(); q.pop();
for (int v = ; v <= ; ++v) {
if (edges[u][v] != - && dist[u] + edges[u][v] < dist[v]) {
if (!visited.count(v)) {
visited.insert(v);
q.push(v);
}
dist[v] = dist[u] + edges[u][v];
}
}
}
}
for (int i = ; i <= N; ++i) {
res = max(res, dist[i]);
}
return res == INT_MAX ? - : res;
}
};

下面来看基于 Bellman-Ford 算法的解法,时间复杂度是 O(VE),V和E分别是结点和边的个数。这种算法是基于 DP 来求全局最优解,原理是对图进行 V - 1 次松弛操作,这里的V是所有结点的个数(为啥是 V-1 次呢,因为最短路径最多只有 V-1 条边,所以只需循环 V-1 次),在重复计算中,使得每个结点的距离被不停的更新,直到获得最小的距离,这种设计方法融合了暴力搜索之美,写法简洁又不失优雅。之前提到了,Bellman-Ford 算法可以处理负权重的情况,但是不能有负环存在,一般形式的写法中最后一部分是检测负环的,如果存在负环则报错。不能有负环原因是,每转一圈,权重和都在减小,可以无限转,那么最后的最小距离都是负无穷,无意义了。没有负环的话,V-1 次循环后各点的最小距离应该已经收敛了,所以在检测负环时,就再循环一次,如果最小距离还能更新的话,就说明存在负环。这道题由于不存在负权重,所以就不检测了,参见代码如下:

解法二:

class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
int res = ;
vector<int> dist(N + , INT_MAX);
dist[K] = ;
for (int i = ; i < N; ++i) {
for (auto e : times) {
int u = e[], v = e[], w = e[];
if (dist[u] != INT_MAX && dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
}
}
}
for (int i = ; i <= N; ++i) {
res = max(res, dist[i]);
}
return res == INT_MAX ? - : res;
}
};

下面这种解法是 Bellman Ford 解法的优化版本,由热心网友旅叶提供。之所以能提高运行速度,是因为使用了队列 queue,这样对于每个结点,不用都松弛所有的边,因为大多数的松弛计算都是无用功。优化的方法是,若某个点的 dist 值不变,不去更新它,只有当某个点的 dist 值被更新了,才将其加入 queue,并去更新跟其相连的点,同时还需要加入 HashSet,以免被反复错误更新,这样的时间复杂度可以优化到 O(E+V)。Java 版的代码在评论区三楼,旅叶声称可以 beat 百分之九十多,但博主改写的这个 C++ 版本的却只能 beat 百分之二十多,hmm,因缺斯汀。不过还是要比上面的解法二快很多,博主又仔细看了看,发现很像解法一和解法二的混合版本哈,参见代码如下:

解法三:

class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
int res = ;
unordered_map<int, vector<pair<int, int>>> edges;
vector<int> dist(N + , INT_MAX);
queue<int> q{{K}};
dist[K] = ;
for (auto e : times) edges[e[]].push_back({e[], e[]});
while (!q.empty()) {
int u = q.front(); q.pop();
unordered_set<int> visited;
for (auto e : edges[u]) {
int v = e.first, w = e.second;
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (visited.count(v)) continue;
visited.insert(v);
q.push(v);
}
}
}
for (int i = ; i <= N; ++i) {
res = max(res, dist[i]);
}
return res == INT_MAX ? - : res;
}
};

讨论:最后再来说说这个 Floyd 算法,这也是一种经典的动态规划算法,目的是要找结点i到结点j的最短路径。而结点i到结点j的走法就两种可能,一种是直接从结点i到结点j,另一种是经过若干个结点k到达结点j。所以对于每个中间结点k,检查 dist(i, k) + dist(k, j) < dist(i, j) 是否成立,成立的话就松弛它,这样遍历完所有的结点k,dist(i, j) 中就是结点i到结点j的最短距离了。时间复杂度是 O(V3),处处透露着暴力美学。除了这三种算法外,还有一些很类似的优化算法,比如 Bellman-Ford 的优化算法- SPFA 算法,还有融合了 Bellman-Ford 和 Dijkstra 算法的高效的多源最短路径算法- Johnson 算法,这里就不过多赘述了,感兴趣的童鞋可尽情的 Google 之~

Github 同步地址:

https://github.com/grandyang/leetcode/issues/743

参考资料:

https://leetcode.com/problems/network-delay-time/description/

https://leetcode.com/problems/network-delay-time/discuss/109982/C++-Bellman-Ford

https://leetcode.com/problems/network-delay-time/discuss/109968/Simple-JAVA-Djikstra's-(PriorityQueue-optimized)-Solution-with-explanation

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Network Delay Time 网络延迟时间的更多相关文章

  1. [LeetCode] Network Delay Time 网络延迟时间——最短路算法 Bellman-Ford(DP) 和 dijkstra(本质上就是BFS的迭代变种)

    There are N network nodes, labelled 1 to N. Given times, a list of travel times as directed edges ti ...

  2. [LeetCode] 743. Network Delay Time 网络延迟时间

    There are N network nodes, labelled 1 to N. Given times, a list of travel times as directededges tim ...

  3. 【LeetCode】743. Network Delay Time 解题报告(Python)

    [LeetCode]743. Network Delay Time 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: ht ...

  4. Java实现 LeetCode 743 网络延迟时间(Dijkstra经典例题)

    743. 网络延迟时间 有 N 个网络节点,标记为 1 到 N. 给定一个列表 times,表示信号经过有向边的传递时间. times[i] = (u, v, w),其中 u 是源节点,v 是目标节点 ...

  5. NFS - Network File System网络文件系统

    NFS(Network File System/网络文件系统): 设置Linux系统之间的文件共享(Linux与Windows中间文件共享采用SAMBA服务): NFS只是一种文件系统,本身没有传输功 ...

  6. 以Network Dataset(网络数据集)方式实现的最短路径分析

    转自原文 以Network Dataset(网络数据集)方式实现的最短路径分析 构建网络有两种方式,分别是网络数据集NetworkDataset和几何网络Geometric Network,这个网络结 ...

  7. SDN(Software Defined Network):软件定义网络----转载

    SDN(Software Defined Network):软件定义网络 传统的网络转发行为: 1)逐设备单独控制,纯分布式控制. 2)控制面和转发面在同一个设备中,耦合紧密. 管理员无法直接操控转发 ...

  8. Docker-Bridge Network 03 自定义网络

    本节介绍自定义bridge network的自定义网络. 1.前言2.创建自定义网络2.1 创建网络2.2 指定网段创建网络3.创建容器3.1 指定网络创建容器3.2 指定IP创建容器4.通信4.1 ...

  9. 如何解决ubuntu 12.04重启后出现waiting for network configuration和网络标志消失问题

    如何解决ubuntu 12.04重启后出现waiting for network configuration和网络标志消失问题 作为菜鸟的我在学着设置网络后,重启电脑后显示 waiting forne ...

随机推荐

  1. HA集群heartbeat配置--Nginx

    HA即(high available)高可用,又被叫做双机热备,用于关键性业务.简单理解就是,两台机器A和B,正常是A提供服务,B待命限制,当A宕机或服务宕掉,会切换至B机器继续提供服务.常用实现高可 ...

  2. CSS奇思妙想图形(心形、气泡三角形、切角、梯形、饼图等)

    今天看到一篇不错文章,在原来CSS3图形创建基础上扩展了很多. 这里记录总结下 心形 原理:利用 圆形 和 正方形实现 HTML: <div class="heartShaped&qu ...

  3. RTMP消息详细介绍

    本文继上篇简单分析了RTMP协议如何进行通信进一步详细分析RTMP的消息都有哪些,以及这些消息有什么作用. 一.RMTP消息 由上一篇文章可知RTMP 消息有分成两个部分,一个是头部,一个是有效负载. ...

  4. c语言最后一次作业

    1.当初你是如何做出选择计算机专业的决定的? 我再来到大学之前,通过查询和询问,了解到当前计算机行业就业需求量较高,同时我对计算机的几年过去比较高了,在高中时期就有过在大学学习计算机行业的知识与专业的 ...

  5. git基本语法

    基本用法(上)               一.实验说明 本节实验为 Git 入门第一个实验,可以帮助大家熟悉如何创建和使用 git 仓库. 二.git的初始化 在使用git进行代码管理之前,我们首先 ...

  6. 项目Beta冲刺Day3

    项目进展 李明皇 今天解决的进度 完善了程序的运行逻辑(消息提示框等) 明天安排 前后端联动调试 林翔 今天解决的进度 向微信官方申请登录验证session以维护登录态 明天安排 继续完成维护登录态 ...

  7. 关于GPUImage的导入

    对于GPUImage的使用方面,GitHub上已经非常详细了,就不一一赘述了,但是对于项目的导入来说,最好的方式是 1.下载GPUImage并解压 2.打开压缩包后如图 3.打开终端,cd到此目录 4 ...

  8. WPF自学入门(十)WPF MVVM简单介绍

     前面文章中,我们已经知道,WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时,WPF会自动发出通知去更新UI. 我们不管 ...

  9. redux的知识点

    Redux: Redux 是针对 JavaScript应用的可预测状态容器 就是用来管理数据的.stroe 保存数据action领导 下达命令reducer员工 执行命令 下载命令:  npm ins ...

  10. 【转】optach学习

    [转自:https://yq.aliyun.com/articles/28007,仅作学习用途] 摘要: Opatch 是oracle公司开发的安装,卸载,检测patch冲突的工具,管理oracle所 ...