leetCode 787. K 站中转内最便宜的航班「中等」

题目

有 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),即为存储状态需要的空间。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你这个代码我看不懂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值