K 站中转内最便宜的航班
描述
- 有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 f l i g h t s [ i ] = [ f r o m i , t o i , p r i c e i ] flights[i] = [from_i, to_i, price_i] flights[i]=[fromi,toi,pricei] ,表示该航班都从城市 f r o m i from_i fromi 开始,以价格 p r i c e i price_i pricei 抵达 t o i to_i 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 <= f r o m i from_i fromi, t o i to_i toi < n
- f r o m i ! = t o i from_i != to_i fromi!=toi
- 1 <= p r i c e i price_i pricei <= 1 0 4 10^4 104
- 航班没有重复,且不存在自环
- 0 <= src, dst, k < n
- src != dst
Typescript 版算法实现
1 )方案 1
function findCheapestPrice(n: number, flights: number[][], src: number, dst: number, k: number): number {
const cheap = (flights: number[][], src: number, dst: number, k: number) => {
// 找到终点站的前一站
const prev: number[][] = flights.filter(item => item[1] === dst)
// 求最小值
const min: number = Math.min.apply(null, prev.map(item => {
// 如果第一项就是起点,直接返回价格
if (item[0] === src && k > -1) {
return item[2]
}
// 这是一个 trick,用int的最大值来替换当前最小值,实际上没有其他数字比这个大,bigInt类型的除外
if (k === 0 && item[0] !== src) {
return Number.MAX_SAFE_INTEGER
}
// 核心公式
return item[2] + cheap(flights, src, item[0], k - 1)
}))
return min
}
// 增加返回值是不是Number.MAX_SAFE_INTEGER,如果是返回-1
const min = cheap(flights, src, dst, k)
return min >= Number.MAX_SAFE_INTEGER ? -1 : min
};
- 上述方案,比官方少了一个参数 n, 但是仍能运行
- 这里核心的分解公式是:F(src,dst,k) = Min(F(src,dst-1, k-1) + F(dst-1, dst, 1))
- 也就是 到 dst目的地,分解成到 src到dst-1和从dist-1到dist的两个过程
- 上述求解过程多了一个边界的判断,用了一个 Number.MAX_SAFE_INTEGER 的小技巧
- 这个算法并没有用到官方给定的参数 n, 也就是 n 个城市
- 这个分解是没有问题的,但是实现方案是有问题的
- 最大的问题是,这种算法是通过递归来实现的,并不是动态规划,容易爆栈,而且会很慢
- 这是一个反面的教材
2 )方案 2
function findCheapestPrice(n: number, flights: number[][], src: number, dst: number, k: number): number {
const dist: number[] = new Array<number>(n).fill(Infinity); // 计算一维数组
dist[src] = 0; // 开始城市记0
// 最多经过k站中转 => 最多经过k+1条边
for(let p:number = 0; p<k+1; p++) {
const prev = Array.from(dist);
// 直接利用flights作为存图信息
// a是from b是to c是price
for(const [a, b, c] of flights) dist[b] = Math.min(dist[b], prev[a] + c);
}
return dist[dst] == Infinity ? -1 : dist[dst];
};
- 这个是官方示例算法
- 这个算法一开始准备一个容量为n的一维数组
- 这个题目设计的精妙在于,起点 src 从0开始 ,终点dst就是最后一个下标
- 这个和我们数组的下标开始正好对应上,所以使用一维数组来存放计算结果
- 我们画图可知,经过k站中转,必定要经过 k+1 条边,这样我们只需要计算 k+1次
- 计算的核心是,求解到达站最小的花费,也就是当前到达站的花费与前一站花多少钱到达这一站,求解出到达当前站的最小花费
- 最终到达每一站都会有一个花费的值,如果仍是占位的 Infinity 说明当前站不可达
- 这就是整个算法的精妙之处
3 )方案 3
function findCheapestPrice(n: number, flights: number[][], src: number, dst: number, k: number): number {
const w = new Array<number[]>(n).fill(null).map(() => new Array<number>(n)); // 初始二维矩阵
const dist = new Array<number>(n);
function BellmanFord(): number {
dist.fill(Infinity);
dist[src] = 0;
// 最多经过k站中转 => 最多经过k+1条边
for(let p: number = 0; p < k + 1; p ++) {
const prev = Array.from(dist);
for(let i:number = 0; i < n; i ++) {
for(let j:number = 0; j<n; j ++) {
dist[j] = Math.min(dist[j], prev[i] + w[i][j]);
}
}
}
return dist[dst];
}
for(let i: number = 0; i < n; i ++) {
for(let j: number = 0; j < n; j ++) {
w[i][j] = i == j ? 0 : Infinity;
}
}
for(const [a, b, c] of flights) w[a][b] = c;
let res = BellmanFord();
return res == Infinity ? -1 : res;
};
- 这个也是官方示例算法, 用了临接矩阵的方式,依赖数学基础
- 整体来说不如 方案2
- 方案2, 3都是在官方给定的示例基础上优化而来的
Python3 版算法实现
1 )方案 1
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
def cheap(flights: list[list[int]], src: int, dst: int, k: int) -> int:
# 找到终点站的前一站
prev = [flight for flight in flights if flight[1] == dst]
# 求最小值
min_cost = float('inf')
for item in prev:
# 如果第一项就是起点,直接返回价格
if item[0] == src and k >= 0:
min_cost = min(min_cost, item[2])
continue
# 如果已无中转次数,且当前节点不是起点,则跳过
if k == 0 and item[0] != src:
continue
# 核心公式
min_cost = min(min_cost, item[2] + cheap(flights, src, item[0], k - 1))
return min_cost
# 增加返回值判断,如果是 float('inf'),则返回 -1
min_cost = cheap(flights, src, dst, k)
return -1 if min_cost == float('inf') else min_cost
- 超出时间限制
2 )方案 2
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
dist = [float('inf')] * n # 初始化距离数组为无穷大
dist[src] = 0 # 源城市到自身的距离为0
# 最多经过k站中转 => 最多经过k+1条边
for p in range(k + 1):
prev = dist.copy() # 复制当前距离数组
# 遍历航班信息更新距离
for flight in flights:
a, b, c = flight # a是from, b是to, c是price
dist[b] = min(dist[b], prev[a] + c)
return -1 if dist[dst] == float('inf') else dist[dst]
3 )方案 3
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
def bellman_ford():
dist = [float('inf')] * n
dist[src] = 0
# 最多经过k站中转 => 最多经过k+1条边
for _ in range(k + 1):
prev = list(dist)
for i in range(n):
for j in range(n):
dist[j] = min(dist[j], prev[i] + w[i][j])
return dist[dst]
w = [[float('inf')] * n for _ in range(n)]
for i in range(n):
for j in range(n):
w[i][j] = 0 if i == j else float('inf')
for a, b, c in flights:
w[a][b] = c
res = bellman_ford()
return -1 if res == float('inf') else res
Golang 版算法实现
1 )方案 1
func findCheapestPrice(n int, flights [][]int, src int, dst int, k int) int {
var cheap func(flights [][]int, src int, dst int, k int) int
cheap = func(flights [][]int, src int, dst int, k int) int {
// 找到终点站的前一站
prev := make([][]int, 0)
for _, item := range flights {
if item[1] == dst {
prev = append(prev, item)
}
}
// 求最小值
min := math.MaxInt32
for _, item := range prev {
// 如果第一项就是起点,直接返回价格
if item[0] == src && k > -1 {
min = int(math.Min(float64(min), float64(item[2])))
continue
}
// 如果已无中转次数,且当前节点不是起点,则跳过
if k == 0 && item[0] != src {
continue
}
// 核心公式
min = int(math.Min(float64(min), float64(item[2]+cheap(flights, src, item[0], k-1))))
}
return min
}
// 增加返回值判断,如果是 math.MaxInt32,则返回 -1
min := cheap(flights, src, dst, k)
if min == math.MaxInt32 {
return -1
}
return min
}
- 超出时间限制
2 )方案 2
func findCheapestPrice(n int, flights [][]int, src int, dst int, k int) int {
dist := make([]int, n)
for i := range dist {
dist[i] = math.MaxInt32 // 初始化距离为最大整数
}
dist[src] = 0 // 开始城市距离为0
// 最多经过k站中转 => 最多经过k+1条边
for p := 0; p <= k; p++ {
prev := make([]int, len(dist))
copy(prev, dist) // 复制当前距离数组到prev
// 遍历航班信息
for _, flight := range flights {
a, b, c := flight[0], flight[1], flight[2]
// 更新到达b城市的最小距离
dist[b] = min(dist[b], prev[a]+c)
}
}
if dist[dst] == math.MaxInt32 {
return -1 // 如果到达dst城市的距离仍然是最大整数,则返回-1
}
return dist[dst]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
3 )方案 3
func findCheapestPrice(n int, flights [][]int, src int, dst int, k int) int {
// 初始化二维矩阵 w,用于存储任意两点之间的最小费用
w := make([][]int, n)
for i := 0; i < n; i++ {
w[i] = make([]int, n)
for j := 0; j < n; j++ {
if i == j {
w[i][j] = 0
} else {
w[i][j] = math.MaxInt32
}
}
}
// 根据航班信息更新 w 矩阵中对应边的权重
for _, flight := range flights {
a, b, c := flight[0], flight[1], flight[2]
w[a][b] = c
}
// 初始化距离数组 dist,用于存储从起点到各点的最小费用
dist := make([]int, n)
for i := range dist {
dist[i] = math.MaxInt32
}
dist[src] = 0
// Bellman-Ford 算法实现
BellmanFord := func() int {
for p := 0; p <= k; p++ {
prev := make([]int, n)
copy(prev, dist)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if w[i][j] != math.MaxInt32 {
dist[j] = min(dist[j], prev[i]+w[i][j])
}
}
}
}
return dist[dst]
}
res := BellmanFord()
if res == math.MaxInt32 {
return -1
}
return res
}
// 辅助函数,返回两个整数中的较小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
335



被折叠的 条评论
为什么被折叠?



