LC2045次短路径花费:「无权图 & BFS & 剪枝」

前言

  • 大家好,我是新人简书博主:「 个人主页」主要分享程序员生活、编程技术、* * 以及每日的LeetCode刷题记录,欢迎大家关注我,一起学习交流,谢谢!
  • 正在坚持每日更新LeetCode每日一题,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~
  • 同时也在进行其他专项类型题目的刷题与题解活动,相关资料也会同步到「GitHub」上面 ~
  • 今天是坚持写题解的28天(haha,从21年圣诞节开始的),大家一起加油

  • 每日一题:LeetCode:2045.到达目的地的第二短时间
    • 时间:2022-01-24
    • 力扣难度:Hard
    • 个人难度:Hard
    • 数据结构:图
    • 是否:BFS、最短路径、Dijkstra
LeetCode每日一题.jpg

2022-01-23:LeetCode:2045.到达目的地的第二短时间

1. 题目描述

  • 题目:原题链接

    • 城市用一个双向连通图表示,图中有 n 个节点,从 1 到 n 编号(包含 1 和 n)。
    • 图中的边用一个二维整数数组 edges 表示,其中每个 edges[i] = [ui, vi] 表示一条节点 ui 和节点 vi 之间的双向连通边。
    • 每组节点对由最多一条边连通,顶点不存在连接到自身的边。穿过任意一条边的时间是 time 分钟。
    • 每个节点都有一个交通信号灯,每 change 分钟改变一次,从绿色变成红色,再由红色变成绿色,循环往复。
    • 所有信号灯都同时改变。你可以在任何时候进入某个节点,但是只能在节点信号灯是绿色时才能离开
    • 如果信号灯是绿色 ,你不能在节点等待,必须离开。
    • 第二小的值 是 严格大于 最小值的所有值中最小的值。例如,[2, 3, 4] 中第二小的值是 3 ,而 [2, 2, 4] 中第二小的值是 4 。
    • 给你 n、edges、time 和 change ,返回从节点 1 到节点 n 需要的 第二短时间 。
    • 你可以任意次穿过任意顶点,包括 1 和 n 。
    • 你可以假设在启程时 ,所有信号灯刚刚变成绿色。
    • 2 <= n <= 10^4
    • n - 1 <= edges.length <= min(2 * 10^4, n * (n - 1) / 2)
    • edges[i].length == 2
    • 1 <= ui, vi <= nui != vi
    • 不含重复边,每个节点都可以从其他节点直接或者间接到达
    • 1 <= time, change <= 10^3
  • 输入输出规范

    • 输入:节点个数、边的连通情况、边的权值时间、红绿灯的周期
    • 输出:到达目的地的第二短的时间
  • 输入输出示例

    • 输入:n = 5, edges = [[1,2],[1,3],[1,4],[3,4],[4,5]], time = 3, change = 5

    • 输出:13

LC2045到达目的地的次短路径示例.png
  • 解释

    • 右图中的蓝色路径是最短时间路径,花费的时间是:
      • 从节点 1 开始,总花费时间=0
      • 1 -> 4:3 分钟,总花费时间=3
      • 4 -> 5:3 分钟,总花费时间=6
    • 右图中的红色路径是第二短时间路径,花费的时间是:
      • 从节点 1 开始,总花费时间=0
      • 1 -> 3:3 分钟,总花费时间=3
      • 3 -> 4:3 分钟,总花费时间=6
      • 在节点 4 等待 4 分钟,总花费时间=10
      • 4 -> 5:3 分钟,总花费时间=13

2. 方法一:BFS

  • 思路

    • 根据题意分析可知,本题是一个双向连通图,且边的权值相等,是一个等权图,或**无权图 **
    • 本题要求的是到达目的地的最短路径(最小花费),虽然除了路径上的权值花费,还有等待红绿灯的额外花费,但是明显可以发现,由于权值相等,路径长的情况下,等待红绿灯的花费也会更多
    • 因此对于无权图的最短路径问题,可以通过BFS广度优先搜索的方式来进行搜索,当第一次搜索的目标节点(终点)时,得到的就是最短路径
    • 本题要求的是第二短的时间花费,即次短路径,那么,只需要求出最短路径,然后下一次搜索到目标节点时就是次短路径了
  • 解题步骤

    • 首先,先要构造一个邻接表储存顶点和边的情况,可以使用Map或者二维数组等
    • 其次,为了得到最短路径次短路径。还需要维护两个集合或数组来存储数据,这里索引表示节点的ID,值表示到达该节点最短时间(也可以存步数,这里因为两者是线性关系都一样)
      • 既可以存到达各个节点时的最短步数、最小花费
      • 也可以存到达各个节点时的具体路径(存一个List)
    • 然后,维护一个队列来实现BFS,队列中存储当前节点的ID到达该节点的时间花费,从起点开始
    • 在BFS的过程中,要根据邻接表找到其下一步可以走向的多个节点,并通过内层循环逐个判断到达这些节点的时间花费,根据花费的时间大小和维护的两个路径数组中的值比较,存储新的更小的值,完成更新
    • 此外,本题还需要考虑等待红绿灯的额外花费,可以通过到达当前节点时的总的时间花费和红绿灯变化周期的余数的奇偶性来进行判断:costTime / change
      • 余数为偶数:表示到达当前节点时为绿灯,无需等待
      • 余数为奇数:表示到达当前节点时为红灯,需要等待 change - costTime % change
      • 注意:总的花费时间 costTime 是要包括已经等待了的时间
  • 题解

    public int secondMinimum(int n, int[][] edges, int time, int change) {
        Map<Integer, List<Integer>> graph = new HashMap<>(); // 邻接表
        // 构建图:邻接表
        for (int[] edge : edges) {
            List<Integer> nodes = graph.getOrDefault(edge[0], new ArrayList<>());
            List<Integer> nodes_ = graph.getOrDefault(edge[1], new ArrayList<>());
            nodes.add(edge[1]);
            graph.put(edge[0], nodes);
            nodes_.add(edge[0]);
            graph.put(edge[1], nodes_);
        }
        // 最短路与次短路
        int[] first = new int[n + 1]; // 到节点i的最短路,步数or时间
        int[] second = new int[n + 1]; // 到节点i的次短路,步数or时间
        Arrays.fill(first, Integer.MAX_VALUE);
        Arrays.fill(second, Integer.MAX_VALUE);
        Deque<int[]> deque = new LinkedList<>(); // int[] {nodeId, costTime}
        deque.add(new int[]{1, 0}); // 起点出发
        while (!deque.isEmpty()) {
            int[] cur = deque.poll();
            int node = cur[0], costTime = cur[1];
            int nextTime = costTime + time;
            List<Integer> nextNodeList = graph.get(node);
            for (int nextNode : nextNodeList) {
                if (nextTime < first[nextNode]) {
                    first[nextNode] =  nextTime;
                    deque.add(new int[]{nextNode, nextTime});
                } else if (nextTime > first[nextNode] && nextTime < second[nextNode]) {
                    second[nextNode] =  nextTime;
                    deque.add(new int[]{nextNode, nextTime});
                }
            }
        }
        // 红绿灯等待时间
        int waitTime = 0;
        for (int i = 1; i < second[n] / time; i++) {
            if ((((waitTime + i * time) / change) & 1) == 1) { // 奇数,红灯
                waitTime += change - (i * time + waitTime) % change;
            }
        }
        // System.out.println(Arrays.toString(first));
        // System.out.println(Arrays.toString(second));
        return second[n] + waitTime;
    }
    
  • 复杂度分析:n 是节点个数、m 是边的个数

    • 时间复杂度:O(n+m)
    • 空间复杂度:O(n+m)

3. 方法二:BFS & 剪枝

  • 思路:剪枝优化

    • 对于方法一的BFS,在搜索的过程中,是会遍历所有的节点和边,不断更新路径数组
      • 外层循环用来遍历队列中的节点,且由于节点间是双向连通的,其中的节点可能会重复
      • 而内层循环用来遍历当前节点可以连接的各个节点,进行更新操作
    • 此时,可以通过两个方面来进行剪枝优化
    • 第一:对于内层循环,中间可能会遍历到目标节点,第一次遍历到目标节点时是最短路径第二次就是次短路径
      • 此后的内层遍历,乃至外层队列的遍历都不需要继续进行了,因为后续的已经是第三短的路径以及其他路径了
      • 因此可以通过设置标识变量来控制跳出循环,也可以通过判断路径数组的目标元素索引处的值是否被更新来实现
    • 第二:另一方面,对于同一个节点,其被访问的次数最多是两次
      • 因为对于最短路径,一定是不会走回头路访问已经访问过的元素的,即一个元素最多访问一次
      • 同样的,对于次短路径,由于要保证比最短路径长,所以可能会重复访问同一个节点,但是也最多只访问两次
      • 因此,通过对元素被访问的次数进行计数,从而减少循环次数
  • 题解

    public int secondMinimum(int n, int[][] edges, int time, int change) {
        Map<Integer, List<Integer>> graph = new HashMap<>(); // 邻接表
        // 构建图:邻接表
        for (int[] edge : edges) {
            List<Integer> nodes = graph.getOrDefault(edge[0], new ArrayList<>());
            List<Integer> nodes_ = graph.getOrDefault(edge[1], new ArrayList<>());
            nodes.add(edge[1]);
            graph.put(edge[0], nodes);
            nodes_.add(edge[0]);
            graph.put(edge[1], nodes_);
        }
        // 最短路与次短路
        int[] first = new int[n + 1]; // 到节点i的最短路,步数or时间
        int[] second = new int[n + 1]; // 到节点i的次短路,步数or时间
        Arrays.fill(first, Integer.MAX_VALUE);
        Arrays.fill(second, Integer.MAX_VALUE);
        int[] visited = new int[n + 1]; // 访问次数计数器
        visited[1]++;
        Deque<int[]> deque = new LinkedList<>(); // int[] {nodeId, costTime}
        deque.add(new int[]{1, 0}); // 起点出发
        while (!deque.isEmpty()) {
            // 剪枝1
            if(second[n] != Integer.MAX_VALUE) break;
            int[] cur = deque.poll();
            int node = cur[0], costTime = cur[1];
            int nextTime = costTime + time;
            List<Integer> nextNodeList = graph.get(node);
            for (int nextNode : nextNodeList) {
                // 剪枝2
                if(visited[nextNode] >= 2) continue;
                if (nextTime < first[nextNode]) {
                    visited[nextNode]++;
                    first[nextNode] = nextTime;
                    deque.add(new int[]{nextNode, nextTime});
                } else if (nextTime > first[nextNode] && nextTime < second[nextNode]) {
                    visited[nextNode]++;
                    second[nextNode] = nextTime;
                    deque.add(new int[]{nextNode, nextTime});
                }
            }
        }
        // 红绿灯等待时间
        int waitTime = 0;
        for (int i = 1; i < second[n] / time; i++) {
            if ((((waitTime + i * time) / change) & 1) == 1) { // 奇数,红灯
                waitTime += change - (i * time + waitTime) % change;
            }
        }
        // System.out.println(Arrays.toString(first));
        // System.out.println(Arrays.toString(second));
        return second[n] + waitTime;
    }
    

最后

如果本文有所帮助的话,欢迎大家可以给个三连「点赞」&「收藏」&「关注」 ~ ~ ~
也希望大家有空的时候光临我的其他平台,上面会更新Java面经、八股文、刷题记录等等,欢迎大家光临交流,谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容