题目大意
有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。
示例 1:
输入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
输出: 200
解释:
城市航班图如下
从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。
解题思路
方法一:
创建一个结构统计图的信息,然后使用队列结构收集当前能够到达的城市,遍历这些城市,找到所能到达的下一个城市,以此类推。
// 创建一个数据结构,用来保存当前城市能够到达的城市编号,航班金钱,以及到达城市的数量
struct city
{
vector<int> number;
vector<int> money;
int neibor = 0;
};
typedef pair<int, int> P;
class Solution {
public:
// 尴尬,超出时间限制
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
if (src == dst)
return 0;
vector<city> nums(n);
// 遍历每一个航班,记录出发城市的信息
// 遍历完成后,得到每个城市能飞到其他城市的航班数量以及价格
for (auto flight : flights){
nums[flight[0]].number.push_back(flight[1]);
nums[flight[0]].money.push_back(flight[2]);
nums[flight[0]].neibor++;
}
// 记录飞到某个城市i的最低金额
unordered_map<int, int> hasVisited;
hasVisited[src] = 0;
// 当前可选择起飞的城市
queue<P> fly;
fly.push({src, 0});
int res = INT_MAX;
// 当前仍有可以起飞的城市,或者仍然可以继续中转
while (!fly.empty() && K >= 0){
--K;
int flyLength = fly.size();
queue<P> tmp;
for (int j = 0; j < flyLength; ++j){
// 当前出发的城市编号,和从src飞到该城市所用的金额
P curFly = fly.front(); fly.pop();
// 得到当前出发城市信息
city curCity = nums[curFly.first];
// 遍历当前出发城市能够一次性到达的邻居城市
for (int i = 0; i < curCity.neibor; ++i){
int thisCityMoney = curFly.second + curCity.money[i];
// 记录飞到该城市的最少金额
if (hasVisited.find(curCity.number[i]) != hasVisited.end())
hasVisited[curCity.number[i]] = min(thisCityMoney, hasVisited[curCity.number[i]]);
else
hasVisited[curCity.number[i]] = thisCityMoney;
// 如果当前城市已经是目的地了,那么就没必要再遍历目的地的邻域了
// 因为到达目的地时肯定是当前路径的最优解,假设构成了一个环,从目的地出去再回来,金额必然增多
if (curCity.number[i] == dst){
res = min(res, thisCityMoney);
continue;
}
// 将当前城市的信息放入队列中
tmp.push({curCity.number[i], thisCityMoney});
}
}
fly = tmp;
}
return res == INT_MAX ? -1 : res;
}
};
方法二:
回溯。同样创建一个图记录航班信息,同时记录哪些城市已经遍历过(遍历过的城市,再次抵达只会使得总金额上升,因此没必要再经过)。
在递归函数中,如果当前城市是目的地,则记录虽少钱数,中止搜索。如果已经经过了K个中转航班仍未到达目的地,或者当前所需金额已经超过了当前最小金额,则没有必要继续探索。
遍历所有城市,如果某个城市没有去过,且能从当前城市直接飞过去,则递归访问该城市。
class Solution {
private:
int res = INT_MAX;
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
if (src == dst)
return 0;
vector<vector<int>> graph(n, vector<int>(n, -1));
for (auto f : flights){
graph[f[0]][f[1]] = f[2];
}
vector<bool> hasVisited(n, false);
hasVisited[src] = true;
dfs(graph, hasVisited, K, dst, src, 0, n);
return res;
}
void dfs(vector<vector<int>> & graph, vector<bool> & hasVisited, int curK, int dst, int curDst, int cost, int n){
if (curDst == dst){
res = min(res, cost);
return ;
}
if (curK < 0 || cost > res)
return ;
for (int i = 0; i < n; ++i){
// 当前点没有访问过,且当前城市到城市i之间能够通航
if (!hasVisited[i] && graph[curDst][i] != -1){
hasVisited[i] = true;
dfs(graph, hasVisited, curK - 1, dst, i, cost + graph[curDst][i], n);
hasVisited[i] = false;
}
}
return ;
}
};
方法三:
动态规划。dp[k][i]表示能够中转k次的情况下,到达i的最少费用。
假设航班f从i–>j,且dp[k-1][i]!=INT_MAX,表示中转k-1次情况下从src出发能够到达i,则通过该航班,中转k次情况下能够从i到j。
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
if (src == dst)
return 0;
// dp[k][i]表示能够中转k次的情况下,到达i的最少费用
vector<vector<int>> dp( K + 2, vector<int>(n, INT_MAX));
// 从src出发,无论中转几次,最少费用一定是0
for (int i = 0; i <= K + 1; ++i)
dp[i][src] = 0;
// 中转k次情况下
for (int k = 1; k <= K + 1; ++k){
// 遍历每一个航班
for (auto f : flights){
// 如果中转k-1次时能够到达f[0]城市,则中转k次情况下就能从f[0]飞到f[1]
if (dp[k - 1][f[0]] != INT_MAX){
// dp[k - 1][f[0]]:中转k-1次时飞到f[0]的最少费用+本次费用
dp[k][f[1]] = min(dp[k][f[1]], dp[k - 1][f[0]] + f[2]);
}
}
}
return dp.back()[dst] == INT_MAX ? -1 : dp.back()[dst];
}
};
这道题应该无法将二维数组转成一维数组,因为dp[k][f[1]] = min(dp[k][f[1]], dp[k - 1][f[0]] + f[2])中,不确定f[1]和f[0]的位置关系。