[LeetCode] Network Delay Time 网络延迟时间
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:
N
will be in the range[1, 100]
.K
will be in the range[1, N]
.- The length of
times
will be in the range[1, 6000]
. - All edges
times[i] = (u, v, w)
will have1 <= u, v <= N
and0 <= 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
LeetCode All in One 题目讲解汇总(持续更新中...)
[LeetCode] Network Delay Time 网络延迟时间的更多相关文章
- [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 ...
- [LeetCode] 743. Network Delay Time 网络延迟时间
There are N network nodes, labelled 1 to N. Given times, a list of travel times as directededges tim ...
- 【LeetCode】743. Network Delay Time 解题报告(Python)
[LeetCode]743. Network Delay Time 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: ht ...
- Java实现 LeetCode 743 网络延迟时间(Dijkstra经典例题)
743. 网络延迟时间 有 N 个网络节点,标记为 1 到 N. 给定一个列表 times,表示信号经过有向边的传递时间. times[i] = (u, v, w),其中 u 是源节点,v 是目标节点 ...
- NFS - Network File System网络文件系统
NFS(Network File System/网络文件系统): 设置Linux系统之间的文件共享(Linux与Windows中间文件共享采用SAMBA服务): NFS只是一种文件系统,本身没有传输功 ...
- 以Network Dataset(网络数据集)方式实现的最短路径分析
转自原文 以Network Dataset(网络数据集)方式实现的最短路径分析 构建网络有两种方式,分别是网络数据集NetworkDataset和几何网络Geometric Network,这个网络结 ...
- SDN(Software Defined Network):软件定义网络----转载
SDN(Software Defined Network):软件定义网络 传统的网络转发行为: 1)逐设备单独控制,纯分布式控制. 2)控制面和转发面在同一个设备中,耦合紧密. 管理员无法直接操控转发 ...
- Docker-Bridge Network 03 自定义网络
本节介绍自定义bridge network的自定义网络. 1.前言2.创建自定义网络2.1 创建网络2.2 指定网段创建网络3.创建容器3.1 指定网络创建容器3.2 指定IP创建容器4.通信4.1 ...
- 如何解决ubuntu 12.04重启后出现waiting for network configuration和网络标志消失问题
如何解决ubuntu 12.04重启后出现waiting for network configuration和网络标志消失问题 作为菜鸟的我在学着设置网络后,重启电脑后显示 waiting forne ...
随机推荐
- Angular开发实践(六):服务端渲染
Angular Universal Angular在服务端渲染方面提供一套前后端同构解决方案,它就是 Angular Universal(统一平台),一项在服务端运行 Angular 应用的技术. 标 ...
- 关于使用Unity开发Kinect时出现的Runtime Error错误的解决方式
一.开发环境: 1. 硬件:Kinect 2.0 2. 操作系统:Windows10 3. Unity版本:5.x以上 4. Kinect SDK:KinectSDK-v2.0_1409 5. Kin ...
- [Java] JDK 环境配置(图文)
Windows10 上的安装配置 1.前往 JDK 官网下载对应 jdk 版本安装包: http://www.oracle.com/technetwork/java/javase/downloads/ ...
- JavaScript判断类型
1.typeof操作符,返回值为字符串,用来判断一个值是哪种基本类型 "undefined"-Undefined "boolean"-Boolean " ...
- Linux下Apache服务的查看和启动
cd到/etc/rc.d/init.d/目录,并列出该目录下的所有文件,看看是否有httpd 使用httpd -v查看已经安装的httpd的版本 使用rpm -qa | grep http ...
- jstree的简单用法
一般我们用jstree主要实现树的形成,并且夹杂的邮件增删重命名刷新的功能 下面是我在项目中的运用,采用的是异步加载 $('#sensor_ul').data('jstree', false).emp ...
- 开始使用HTML5和CSS3验证表单
使用HTML5和CSS3验证表单 客户端验证是网页客户端程序最常用的功能之一,我们之前使用了各种各样的js库来进行表单的验证.HTML5其实早已为我们提供了表单验证的功能.至于为啥没有流行起来估计是兼 ...
- 前端双引号单引号,正则反向引用,js比较jq
1.js,jq,css,html属性必须双,如果同时出现需要嵌套使用,属性的规范是双但是也可以用单测试有效 单引号现象举例:jq中获取元素标签是单引号:$('input').click:弹出也是单引号 ...
- pdf解析与结构化提取
#PDF解析与结构化提取##PDF解析对于PDF文档,我们选择用PDFMiner对其进行解析,得到文本.###PDFMinerPDFMiner使用了一种称作lazy parsing的策略,只在需要的时 ...
- Java服务器端生成报告文档:使用SQL Server Report Service(SSRS)
SQL Server Report Service(SSRS)提供了Asp.Net和WinForm两类客户端组件封装,因此使用C#实现SSRS报表的导出功能,仅需要使用相应的组件即可. Java操作S ...