力扣2045.-到达目的地的第二短时间

第八十六天 --- 力扣2045.-到达目的地的第二短时间

题目一

力扣:2045.-到达目的地的第二短时间

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路:SPFA(BFS)

为什么选择SPFA

1、很明显,通过读题,我们一下就能看出来这是有关最短路径问题,但却不是正常的最短路径,而是次短路径。

2、回顾Dijkstra,每一次都是从dis中找到最小值(即离源点最近的点),用这个点再去更新别的未访问点,这样的话仅仅维护一个最短路径和容易,但是还要维护一个次短路径,这样的话就比较困难了,得改变很多思路,出现很多问题。

3、而SPFA则不一样,他每次只维护那些可能接下来对当前局势产生变化的点,不存在说每次找dis中为访问过的点中最小的这回事,点和点之间平等,用动态逼近的方法求出最短路径,那么这题要次短路径,我们只需要每次多维护一个次短序列,每次选好目标点之后,用两个距离(最短&次短)一起更新能去到的点的两个距离即可。说白了就是从原来只维护一个数组,变成了维护两个;从一次只拿最短长度进行更新变成了一次拿最短&次短长度对两个数组同时更新维护,被更新的点进来即可。

4、所以选择算法一定不要太拘泥于规则,要灵活多变,算法很多,每道题都要找到合适的才行,别看SPFA有很多弊端,容易被卡数据,但那是在ACM那种竞赛级别的考试才会干的事,咱们这是找工作,这种水平的题目是不会出那种数据的,所以放心地用,只要感觉这题是这个路子,就放心大胆地冲!

实现

1、我的另一篇博客:图论最短路径专题讲解里面详细的写了正常SPFA算法的思路做法,读者可以先去那里看看,之后再回来读下面的文章。

2、主题思路的变化,其实就是从原来只维护一个数组,变成了维护两个;从一次只拿最短长度进行更新变成了一次拿(最短&次短)长度对两个数组同时更新维护,被更新的点入队即可。

代码(邻接表)

注意:
1、因为这个题有红灯的存在,所以会导致时间上出差错,这里吧处理办法说一下:

<1>首先是dis_first&dis_second存储的是到达某个点的时间,一定记住这点!想从这个点出发,就有说到了,到达这个点的时候很可能赶上红灯就得等红灯,那么出发时间就和到达时间不一致了,所以选定了某个点后,从这个点出发的时间并不一定是存好的到达时间,要单独计算。注意,如果是到达时间为inf说明没到过呢,不能计算出发时间。

<2>出发时间计算方法:
到达时间item,先除change,得到的就是经过的变化次数,因为开始的时候是绿灯,所以变化次数是偶数,说明到达时是绿灯可以直接过,否则是红灯,就得等。等待时间不必单独加,直接(item/change+1)*change即可

class Solution {
    
    
public:

	int tranTimes(int change, int item) {
    
    //计算出发时间,方法在上
		int tmp = item / change;
		if (tmp % 2 == 1) {
    
    
			return (tmp + 1)*change;
		}
		return item;
	}

	const int inf = 1 << 28;//定义无穷大
	int secondMinimum(int n, vector<vector<int>>& edges, int time, int change) {
    
    
		vector<vector<int>> edge(n);//图
		vector<int> dis_first(n, inf);//最短路
		vector<int> dis_second(n, inf);//次短路
		int size_edges = edges.size();

		for (int i = 0; i < size_edges; i++) {
    
    //建图且为无向图
			int u = edges[i][0];
			int v = edges[i][1];
			edge[u - 1].push_back(v - 1);
			edge[v - 1].push_back(u - 1);
		}

		queue<int> record;
		record.push(0);
		dis_first[0] = 0;//只有源点到自己最短路是0,别的是正无穷
		
		/*SPFA改 开始*/
		while (!record.empty()) {
    
    
			int point = record.front();//取出当前点
			record.pop();

			int first = dis_first[point];//取出到达时间
			int second = dis_second[point];
			if (first != inf) {
    
    //没到达不能算出发时间
				first = tranTimes(change, first);//算出发时间
			}
			if (second != inf) {
    
    //没到达不能算出发时间
				second = tranTimes(change, second);//算出发时间
			}

			for (int e : edge[point]) {
    
    
			    //原来是维护一个数组,现在是维护两个数组
			    //原来是用一个最小值就行了,现在同时用最小&次小值来共同维护这两个数组就行,共4次
			    //再多一个第三小就以此类推,就得一共维护九次-3*3
			    //最小&次小值更新方法,略
				if (first + time < dis_first[e]) {
    
    //注意这里范围一定要严格定义,原因请读题
					dis_second[e] = dis_first[e];
					dis_first[e] = first + time;
					record.push(e);//更新就入队
				}
				else if (first + time > dis_first[e] && first + time < dis_second[e]) {
    
    
				//注意这里范围一定要严格定义,原因请读题
					dis_second[e] = first + time;
					record.push(e);//更新就入队
				}

				if (second + time < dis_first[e]) {
    
    
				//注意这里范围一定要严格定义,原因请读题
					dis_second[e] = dis_first[e];
					dis_first[e] = second + time;
					record.push(e);//更新就入队
				}
				else if (second + time > dis_first[e] && second + time < dis_second[e]) {
    
    
				//注意这里范围一定要严格定义,原因请读题
					dis_second[e] = second + time;
					record.push(e);//更新就入队
				}
			}
		}

		return dis_second[n - 1];//找答案
	}
};

所有代码均以通过力扣测试
(经过多次测试最短时间为):
在这里插入图片描述

Guess you like

Origin blog.csdn.net/qq_45678698/article/details/120874402