题目
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -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,如图中红色所示。
示例 2:
输入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
输出: 500
解释:
城市航班图如下
从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。
提示:
1 <= n <= 100
0 <= flights.length <= (n * (n - 1) / 2)
flights[i].length == 3
0 <= fromi, toi < n
fromi != toi
1 <= pricei <= 104
航班没有重复,且不存在自环
0 <= src, dst, k < n
src != dst
题解
方法:动态规划
思路与算法
我们用 f[t][i] 表示通过恰好 t 次航班,从出发城市 src 到达城市 i 需要的最小花费。在进行状态转移时,我们可以枚举最后一次航班的起点 j,即:
其中 (j, i) ∈flights 表示在给定的航班数组 flights 中存在从城市 j 出发到达城市 i 的航班,cost(j,i) 表示该航班的花费。该状态转移方程的意义在于,枚举最后一次航班的起点 j,那么前 t-1 次航班的最小花费为 f[t-1][j] 加上最后一次航班的花费 cost(j,i) 中的最小值,即为 f[t][i]。
由于我们最多只能中转 k 次,也就是最多搭乘 k+1 次航班,最终的答案即为
中的最小值。
细节
当 t=0 时,状态 f[t][i] 表示不搭乘航班到达城市 i 的最小花费,因此有:
也就是说,如果 i 是出发城市 src,那么花费为 0;否则 f[0][i] 不是一个合法的状态,由于在状态转移方程中我们需要求出的是最小值,因此可以将不合法的状态置为极大值 ∞。根据题目中给出的数据范围,航班的花费不超过 10^4 ,最多搭乘航班的次数 k+1 不超过 101,那么在实际的代码编写中,我们只要使得极大值大于 10^4 × 101,就可以将表示不合法状态的极大值与合法状态的花费进行区分。
在状态转移中,我们需要使用二重循环枚举 t 和 i,随后枚举所有满足 (j,i)∈flights 的 j,这样做的劣势在于没有很好地利用数组 flights,为了保证时间复杂度较优,需要将 flights 中的所有航班存储在一个新的邻接表中。一种可行的解决方法是,我们只需要使用一重循环枚举 t,随后枚举 flights 中的每一个航班 (j,i,cost),并用 f[t−1][j]+cost 更新 f[t][i],这样就免去了邻接表的使用。
注意到 f[t][i] 只会从 f[t−1][..] 转移而来,因此我们也可以使用两个长度为 n 的一维数组进行状态转移,减少空间的使用。
代码
下面的代码使用二维数组进行状态转移。
// C++
class Solution {
private:
static constexpr int INF = 10000 * 101 + 1;
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
vector<vector<int>> f(k + 2, vector<int>(n, INF));
f[0][src] = 0;
for (int t = 1; t <= k + 1; ++t) {
for (auto&& flight: flights) {
int j = flight[0], i = flight[1], cost = flight[2];
f[t][i] = min(f[t][i], f[t - 1][j] + cost);
}
}
int ans = INF;
for (int t = 1; t <= k + 1; ++t) {
ans = min(ans, f[t][dst]);
}
return (ans == INF ? -1 : ans);
}
};
复杂度分析
时间复杂度:O((m+n)k),其中 m 是数组 flights 的长度。状态的数量为 O(nk),对于固定的 t,我们需要 O(m) 的时间计算出所有 f[t][..] 的值,因此总时间复杂度为 O((m+n)k)。
空间复杂度:O(nk) 或 O(n),即为存储状态需要的空间。