2045. 到达目的地的第二短时间 :「堆优化 Dijkstra」&「BFS」

简介: 2045. 到达目的地的第二短时间 :「堆优化 Dijkstra」&「BFS」

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 2045. 到达目的地的第二短时间 ,难度为 困难


Tag : 「最短路」、「BFS」、「堆优化 Dijkstra」


城市用一个 双向连通 图表示,图中有 nn 个节点,从 11nn 编号(包含 11nn)。图中的边用一个二维整数数组 edgesedges 表示,其中每个 edges[i] = [u_i, v_i]edges[i]=[ui,vi] 表示一条节点 u_iui 和节点 v_ivi 之间的双向连通边。每组节点对由 最多一条 边连通,顶点不存在连接到自身的边。穿过任意一条边的时间是 timetime 分钟。


每个节点都有一个交通信号灯,每 changechange 分钟改变一次,从绿色变成红色,再由红色变成绿色,循环往复。所有信号灯都 同时 改变。你可以在 任何时候 进入某个节点,但是 只能 在节点 信号灯是绿色时 才能离开。如果信号灯是  绿色 ,你 不能 在节点等待,必须离开。


第二小的值 是 严格大于 最小值的所有值中最小的值。


  • 例如,[2, 3, 4] 中第二小的值是 33 ,而 [2, 2, 4] 中第二小的值是 44


给你 nnedgesedgestimetimechangechange ,返回从节点 11 到节点 nn 需要的 第二短时间 。


注意:


你可以 任意次 穿过任意顶点,包括 1 和 n 。 你可以假设在 启程时 ,所有信号灯刚刚变成 绿色 。


示例 1:


网络异常,图片无法展示
|


输入:n = 5, edges = [[1,2],[1,3],[1,4],[3,4],[4,5]], time = 3, change = 5
输出:13
解释:
上面的左图展现了给出的城市交通图。
右图中的蓝色路径是最短时间路径。
花费的时间是:
- 从节点 1 开始,总花费时间=0
- 1 -> 4:3 分钟,总花费时间=3
- 4 -> 5:3 分钟,总花费时间=6
因此需要的最小时间是 6 分钟。
右图中的红色路径是第二短时间路径。
- 从节点 1 开始,总花费时间=0
- 1 -> 3:3 分钟,总花费时间=3
- 3 -> 4:3 分钟,总花费时间=6
- 在节点 4 等待 4 分钟,总花费时间=10
- 4 -> 5:3 分钟,总花费时间=13
因此第二短时间是 13 分钟。    
复制代码


示例 2:


网络异常,图片无法展示
|


输入:n = 2, edges = [[1,2]], time = 3, change = 2
输出:11
解释:
最短时间路径是 1 -> 2 ,总花费时间 = 3 分钟
最短时间路径是 1 -> 2 -> 1 -> 2 ,总花费时间 = 11 分钟
复制代码


提示:


  • 2 <= n <= 10^42<=n<=104
  • n - 1 <= edges.length <= min(2 * 10^4, n * (n - 1) / 2)n1<=edges.length<=min(2104,n(n1)/2)
  • edges[i].length == 2edges[i].length==2
  • 1 <= u_i, v_i <= n1<=ui,vi<=n
  • u_i != v_iui!=vi
  • 不含重复边
  • 每个节点都可以从其他节点直接或者间接到达
  • 1 <= time, change <= 10^31<=time,change<=103


堆优化 Dijkstra



整体题意:在一张正权无向图上求严格次短路,该图无重边与自环。


同时根据提示 edges.length <= \min(2 * 10^4, n * (n - 1) / 2)edges.length<=min(2104,n(n1)/2) 可知,该图为「稀疏图」,容易想到「堆优化 Dijkstra」做法。


对「堆优化 Dijkstra」或者「其他最短路算法」不熟悉的同学,可以看前置 🧀 :【最短路/必背模板】涵盖所有的「存图方式」与「最短路算法(详尽注释)」。内容如题,首次接触的话,建议每个模板先敲十遍。


回到本题,与常规的「求最短路」不同,本题需要求得「严格次短路」,我们可以原来的最短路算法基础上(dist1[]dist1[] 数组用于记录最短路)多引入一个 dist2[]dist2[] 数组,dist2[x]dist2[x] 用于记录从节点 11 到节点 xx 的严格次短路。


维护次短路是容易的,基本思路为:


  • 若当前距离 distdist 小于 dist1[x]dist1[x],原本的最短路 dist1[x]dist1[x] 沦为次短路 dist2[x]dist2[x],即先用 dist1[x]dist1[x] 更新 dist2[x]dist2[x] 后,再用 distdist 更新 dist1[x]dist1[x]
  • 若当前距离 distdist 等于 dist1[x]dist1[x],不符合「严格次短路」,忽略;
  • 若当前距离 distdist 大于 dist1[x]dist1[x],且 distdist 小于 dist2[x]dist2[x],则使用 distdist 更新 dist2[x]dist2[x]


同时,由于处理「严格次短路包含重复边」的情况,我们无须使用 vis[]vis[] 数组记录处理过的点,而要确保每次「最短路」或者「次短路」被更新时,都进行入堆操作。


然后考虑「红绿灯」切换问题,这本质是引入动态边权,假设我们当前经过 stepstep 步到达节点 ii,根据其与 changechange 的关系分情况讨论即可:


  • \left \lfloor \frac{step}{change} \right \rfloorchangestep 为偶数:当前处于绿灯,动态边权为 00
  • \left \lfloor \frac{step}{change} \right \rfloorchangestep 为奇数:当前处于红灯,需要增加动态边权(等待时间),增加的动态边权为 change - (step % change)


最后,为了避免每个样例都 new 大数组,我们可以使用 static 修饰需要用到的数据,并在执行逻辑前进行重置工作。


代码:


class Solution {
    static int N = 10010, M = 4 * N, INF = 0x3f3f3f3f, idx = 0;
    static int[] he = new int[N], e = new int[M], ne = new int[M];
    static int[] dist1 = new int[N], dist2 = new int[N];
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx;
        idx++;
    }
    public int secondMinimum(int n, int[][] edges, int time, int change) {
        Arrays.fill(dist1, INF);
        Arrays.fill(dist2, INF);
        Arrays.fill(he, -1);
        idx = 0;
        for (int[] e : edges) {
            int u = e[0], v = e[1];
            add(u, v); add(v, u);
        }
        PriorityQueue<int[]> q = new PriorityQueue<>((a,b)->a[1]-b[1]);
        q.add(new int[]{1, 0});
        dist1[1] = 0;
        while (!q.isEmpty()) {
            int[] poll = q.poll();
            int u = poll[0], step = poll[1];
            for (int i = he[u]; i != -1; i = ne[i]) {
                int j = e[i];
                int a = step / change, b = step % change;
                int wait = a % 2 == 0 ? 0 : change - b;
                int dist = step + time + wait;
                if (dist1[j] > dist) {
                    dist2[j] = dist1[j];
                    dist1[j] = dist;
                    q.add(new int[]{j, dist1[j]});
                    q.add(new int[]{j, dist2[j]});
                } else if (dist1[j] < dist && dist < dist2[j]) {
                    dist2[j] = dist;
                    q.add(new int[]{j, dist2[j]});
                }
            }
        }
        return dist2[n];
    }
}
复制代码


  • 时间复杂度:令 nn 为点数,mm 为边数,堆优化 Dijkstra 的复杂度为 O(m\log{n})O(mlogn)
  • 空间复杂度:O(n + m)O(n+m)


BFS



更进一步,原图边权虽然由「固定部分 timetime」和「动态部分 changechange 相关」所组成,但在路径固定的前提下,其实边权之和完全确定。


因此我们可以先将原图等价为边权为 11 的新图,通过 BFS 求出最短路 dist1[x]dist1[x] 和严格次短路 dist2[x]dist2[x],然后利用此时的 dist2[n]dist2[n] 其实是严格次短路的边数,计算原图上的边权之和。


代码:


class Solution {
    static int N = 10010, M = 4 * N, INF = 0x3f3f3f3f, idx = 0;
    static int[] he = new int[N], e = new int[M], ne = new int[M];
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx;
        idx++;
    }
    public int secondMinimum(int n, int[][] edges, int time, int change) {
        Arrays.fill(he, -1);
        idx = 0;
        for (int[] e : edges) {
            int u = e[0], v = e[1];
            add(u, v); add(v, u);
        }
        Deque<int[]> d = new ArrayDeque<>();
        int[] dist1 = new int[n + 10], dist2 = new int[n + 10];
        Arrays.fill(dist1, INF);
        Arrays.fill(dist2, INF);
        d.addLast(new int[]{1, 0});
        dist1[1] = 0;
        while (!d.isEmpty()) {
            int[] poll = d.pollFirst();
            int u = poll[0], dist = poll[1];
            for (int i = he[u]; i != -1; i = ne[i]) {
                int j = e[i];
                if (dist1[j] > dist + 1) {
                    dist2[j] = dist1[j];
                    dist1[j] = dist + 1;
                    d.addLast(new int[]{j, dist1[j]});
                    d.addLast(new int[]{j, dist2[j]});
                } else if (dist1[j] < dist + 1 && dist + 1 < dist2[j]) {
                    dist2[j] = dist + 1;
                    d.addLast(new int[]{j, dist2[j]});
                }
            }
        }
        int ans = 0;
        for (int i = 0; i < dist2[n]; i++) {
            int a = ans / change, b = ans % change;
            int wait = a % 2 == 0 ? 0 : change - b;
            ans += time + wait;
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:令 nn 为点数,mm 为边数,BFS 的复杂度为 O(n + m)O(n+m)
  • 空间复杂度:O(n + m)O(n+m)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.2045 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
7月前
|
存储 分布式数据库 Apache
小米基于 Apache Paimon 的流式湖仓实践
本文整理自Flink Forward Asia 2024流式湖仓专场分享,由计算平台软件研发工程师钟宇江主讲。内容涵盖三部分:1)背景介绍,分析当前实时湖仓架构(如Flink + Talos + Iceberg)的痛点,包括高成本、复杂性和存储冗余;2)基于Paimon构建近实时数据湖仓,介绍其LSM存储结构及应用场景,如Partial-Update和Streaming Upsert,显著降低计算和存储成本,简化架构;3)未来展望,探讨Paimon在流计算中的进一步应用及自动化维护服务的建设。
371 0
小米基于 Apache Paimon 的流式湖仓实践
|
9月前
|
存储 安全 算法
陪玩系统功能 陪玩平台 陪玩系统用户体验 陪玩系统安全性 陪玩系统开发
陪玩系统旨在为用户寻找合适的陪玩者,提供注册登录、资料展示、搜索匹配、预约支付、实时沟通及评价反馈等功能。平台拥有丰富的陪玩资源,便捷的预约流程,安全的支付环境和良好的用户体验。系统通过优化算法、提升沟通效率、丰富服务内容和建立社区互动来提升用户体验。安全性方面,系统采用数据加密、防火墙、支付安全和实名认证等措施。开发过程包括需求分析、系统设计、前后端开发、测试优化和上线推广。
845 2
|
JavaScript 前端开发 安全
JavaScript基础-函数定义与调用
【6月更文挑战第11天】本文介绍了JavaScript中的函数,包括函数声明、函数表达式和箭头函数的定义方式,强调了函数调用的注意事项,如参数处理。同时,讨论了三个常见易错点:作用域与闭包、this指向和参数处理,并提供了避免这些问题的方法。通过代码示例展示了参数验证,以提高代码健壮性。理解并掌握这些基础和技巧,有助于提升JavaScript编程水平。
169 3
|
10月前
|
搜索推荐 物联网 Android开发
移动应用与系统
在数字化时代,移动应用和操作系统成为了我们日常生活的重要组成部分。本文将探讨移动应用开发和移动操作系统的相关话题,包括它们的发展历程、当前趋势以及未来的挑战。通过深入了解这些内容,我们可以更好地理解移动技术对我们生活的影响,并思考如何在这个快速发展的领域中保持竞争力。
|
12月前
|
SQL 安全 算法
网络安全的盾牌与矛:加密技术与安全意识的双重防线
【9月更文挑战第28天】在数字世界的海洋中,网络安全是守护我们数据宝库的盾与矛。本文深入探讨了网络安全的两个核心要素:加密技术和安全意识。通过揭示常见的网络漏洞、分析加密技术的工作原理以及提升个人和组织的安全意识,我们旨在为读者提供一套全面的网络安全知识框架。从理解基础概念到实施有效策略,本文将引导您走在信息安全的前沿,确保在这个信息迅速流动的时代,您的数据依然坚固如初。
|
SQL 关系型数据库 MySQL
【MySQL】脏读、不可重复读、幻读介绍及代码解释
【MySQL】脏读、不可重复读、幻读介绍及代码解释
|
前端开发 Java 数据处理
【Netty】Netty 异步任务模型 及 Future-Listener 机制
【Netty】Netty 异步任务模型 及 Future-Listener 机制
910 0
【Netty】Netty 异步任务模型 及 Future-Listener 机制
|
前端开发 JavaScript Java
小白版SpringMVC执行流程
小白版SpringMVC执行流程
|
存储 缓存 算法
《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——第3章 数据流 3.1同步数据流
Ptolemy II 能够使异构系统的开发和仿真一同进行,将开发和仿真作为整个系统建模的一部分。正如前两章讨论的那样,不同于其他设计和建模环境,Ptolemy II的一个关键创新在于支持多种计算模型,这些计算模型可被剪裁以适应具体的建模问题。
1857 0
|
搜索推荐 算法 索引
“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(中)
“掌握更多的快速排序技巧:三路划分、双路快排和非递归的深入理解”(中)
208 0