第八十六天 --- 力扣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];//找答案
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):