题意分析
787. K 站中转内最便宜的航班 - 力扣(LeetCode)
这道题乍一看就是一个加了限制的dijkstra算法,硬改的话到每个节点的最短距离数组要变成二维,表示走了的站数和对应的最短距离,超过的站数部分就不要了。不过最好的解法是bellman-ford,这个算法就是以边来限定顶点的最短距离,而且最多遍历n-1次就能取得每个顶点的最短路径。
问题是更新k次,与满足最大中转站数为k站有什么关系吗?
可以以最坏的情况考虑,每次遍历只能更新一个站,那么由等式
dist[i][y]=min(dist[i][y],dist[i-1][x]+d);
其中i为遍历次数,y为当前要更新的顶点,x为与y相邻的前一个顶点。可以发现每次遍历y与起始点都可能会“插入”一个x来使距离更短,就相当于插入了一个中转站。所以遍历k次到终点的最大中转站只能是k个。
那如果每次遍历都能更新多个站呢,那也是一层内的更新,相当于多个最坏情况的子树,所以最大中转站还是k个。
算法思路
bellman-ford算法就是给定更新层数,每次都把边遍历一遍,找可以更新的边权值。由于最大为k个中转站,也就是说路上有k+2个顶点(包起终点),那么就只要k+1次遍历就一定能更新完路径,只要是连通的就可得解。
代码实现
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
const int inf=INT_MAX/2;
vector<vector<int>> dist(k+2,vector<int>(n,inf));
dist[0][src]=0;
int res=inf;
for(int t=1;t<=k+1;t++){
for(auto & fly:flights){
int a=fly[0],b=fly[1],c=fly[2];
dist[t][b]=min(dist[t][b],dist[t-1][a]+c);//前一顶点之前的遍历结果+当前边权
if(b==dst) res=min(res,dist[t][b]);
}
}
return res==inf?-1:res;
}
};
错误样例
typedef pair<int,int> PII;
typedef array<int,3> AI3;
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
const int inf=INT_MAX/2;
vector<vector<int>> dist(k+2,vector<int>(n,inf));
vector<vector<PII>> g(n);
for(auto & f:flights){
int a=f[0],b=f[1],c=f[2];
g[a].emplace_back(c,b);
}
queue<AI3> q;
q.push({0,src,0});
int res=inf;
int step=0;
while(step<k+1){
step++;
int size=q.size();
while(size--){
auto [d,i,t]=q.front();q.pop();
dist[t][i]=min(dist[t][i],d);
for(auto & [w,nxt]:g[i]){
if(t+1>k+1) continue;
dist[t+1][nxt]=min(dist[t+1][nxt],d+w);
q.push({d+w,nxt,t+1});
}
}
}
for(int i=0;i<=k+1;i++){
res=min(res,dist[i][dst]);
}
return res==inf?-1:res;
}
};
我觉着就是个层序遍历,多加个次数维度,次数大了就甩掉,但是运行超时了 。。。可能层序遍历枚举情况太多了吧。。。以后再来改吧。
解题总结
bellman-ford算法适合负权、带限制顶点数的图。相关练习会另外补充。