力扣787-K 站中转内最便宜的航班2021-10-14

第八十二天 --- 力扣787-K 站中转内最便宜的航班

题目一

力扣:787

在这里插入图片描述

在这里插入图片描述

思路

1、这个题,求得其实就是让花费最少的路径问题,并且只有一个固定源头src,所以乍一看们就是 Dijkstra 算法,但是请看题目,有一个k的限制,即所到达的中间节点不可以超过个k个,属于有限制的最短路径问题。这件事Dijkstra 就办不到了,朴素的Dijkstra 只能为我们找到最短路径,但是路径内部不好判断。
2、再次看题,假设当前航班要到点i,那么他只能从和他相邻的点飞过去,票价是之前的加上本次票价,这不正好是DP问题思路吗。
3、Bellman Ford/SPFA 都是基于动态规划,在对所求路径无限制时候,状态定义为f[i][k],代表从起点到 i点且经过最多 k条边的最短路径。这样的状态定义引导我们能够使用 Bellman Ford 来解决有边数限制的最短路问题。
4、同样的,多源汇最短路算法 Floyd 也是基于动态规划,其原始的三维状态定义为 f[i][j][k] 代表从点 i 到点 j,且经过的所有点编号不会超过 k(即可使用点编号范围为 [1, k][1,k])的最短路径。这样的状态定义引导我们能够使用 Floyd 求最小环或者求“重心点”(即删除该点后,最短路值会变大)。
5、所以遇到了带有限制的最短路径问题的时候,就要尽量的往由DP定义出来的算法上思考,在没限制的基础上,加装相应的状态来DP求解。就不能使用贪心思想的dijkstra了。

定状态

1、因为要寻找到一条路径,所以一定有开始节点和终止节点。但是我们要找的路径他必定是从src开始的,所以路径就可以表示成从src开始,现在走到了i的路径,所以只要一个状态i即可表示
2、因为有k的限制,所以必须爱得有一个状态用于表示k。一个答案路径,k个中间节点,k+1条边,并且边个数>=1,所以为了让转移方程式中不越界,所以用t表示经过边数。

定初值

只有dp[0][src] = 0;即在初始点哪里都没去,是0花费,剩下的点有的没意义,有的代求,没意义的点也得配合求出最小值,所以初始值就要大于可能出现的最大值,为10000 * 101 + 1;(看题目提示)

找转移

(我们在思考转移的时候,可以适当根据具体例子走。)
dp[t][n]代表该路径经过了t条边,从src到n,能到达n的路径,只能是走那些到达点是n路径,所以dp[t][n] = min(dp[t][n], dp[t - 1][m] + price);,所以枚举所有到达点是n路径,m为那些边的起点,因为mn之间差一条路径,所以是t-1,这里要-1,如果用题目中的k概念,容易越界,所以用了路径上边的个数模拟k。

找答案

该题就是找一条src到dst的路径,所以最后第二维就是dst,因为到了dst有很多种路径,其经过的边数不一样,最多不超过k+1所以要枚举找最小。

for (int i = 1; i <= k + 1; i++) {
	ans = min(ans, dp[i][dst]);
}

附加

1、如果大家还没懂,请见:图论之最短路径专题,这里有更加详细地介绍了Bellman-ford算法。
2、本题恰好符合Bellman-Ford算法,只不过,我在找路径长度的时候,最多只能找到k+1条,只有这里被限制了,别的思想一摸一样

代码

注意:由于 Bellman Ford 核心操作需要遍历所有的边,因此也可以直接使用 flights数组作为存图信息,而无须额外存图。(如果不太明白,可以自己按照造邻接表的方法再来一遍,发现其实二者是一样的,都是遍历了所有的边)

class Solution {
public:
	const int MAX_PRICE = 10000 * 101 + 1;//根据题意算出最大值
	int dp[101][101];
	int ans = MAX_PRICE;//初始化
	int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
		for (int i = 0; i < 101; i++) {//初始化
			for (int j = 0; j < 101; j++) {
				dp[i][j] = MAX_PRICE;
			}
		}
		dp[0][src] = 0;//初始化
		for (int t = 1; t <= k + 1; t++) {//枚举t
			for (int i = 0; i < flights.size(); i++) {//枚举flights
				int m = flights[i][0];
				int n = flights[i][1];
				int price = flights[i][2];
				dp[t][n] = min(dp[t][n], dp[t - 1][m] + price);//针对每条边来一个转移
			}
		}
		for (int i = 1; i <= k + 1; i++) {
			ans = min(ans, dp[i][dst]);//找答案
		}
		return ans == MAX_PRICE ? -1 : ans;
	}
};

(所有代码均已在力扣上运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):

在这里插入图片描述
在这里插入图片描述
因为并没有采取类似于滚动数组优化,即只采用两个一维数组情况,所以空间复杂度是O(kn)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JLU_LYM

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

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

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

打赏作者

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

抵扣说明:

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

余额充值