leetcode787.K站中转内最便宜的航班

题目大意

有 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 到城市 21 站中转以内的最便宜价格是 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]的位置关系。

猜你喜欢

转载自blog.csdn.net/qq_41092190/article/details/106924321