There are n
cities connected by m
flights. Each fight starts from city u
and arrives at v
with a price w
.
Now given all the cities and fights, together with starting city src
and the destination dst
, your task is to find the cheapest price from src
to dst
with up to k
stops. If there is no such route, output -1
.
Example 1: Input: n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] src = 0, dst = 2, k = 1 Output: 200 Explanation: The graph looks like this:
The cheapest price from city0
to city2
with at most 1 stop costs 200, as marked red in the picture.
Example 2: Input: n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] src = 0, dst = 2, k = 0 Output: 500 Explanation: The graph looks like this:
The cheapest price from city0
to city2
with at most 0 stop costs 500, as marked blue in the picture.
Note:
- The number of nodes
n
will be in range[1, 100]
, with nodes labeled from0
ton
- 1
. - The size of
flights
will be in range[0, n * (n - 1) / 2]
. - The format of each flight will be
(src,
dst
, price)
. - The price of each flight will be in the range
[1, 10000]
. k
is in the range of[0, n - 1]
.- There will not be any duplicated flights or self cycles.
這道題給了我們一些航班信息,包括出發地,目的地,和價格,然后又給了我們起始位置和終止位置,說是最多能轉K次機,讓我們求出最便宜的航班價格。那么實際上這道題就是一個有序圖的遍歷問題,博主最先嘗試的遞歸解法由於沒有做優化,TLE了,實際上我們可以通過剪枝處理,從而壓線過OJ。首先我們要建立這個圖,選取的數據結構就是鄰接鏈表的形式,具體來說就是建立每個結點和其所有能到達的結點的集合之間的映射,然后就是用DFS來遍歷這個圖了,用變量cur表示當前遍歷到的結點序號,還是當前剩余的轉機次數K,訪問過的結點集合visited,當前累計的價格out,已經全局的最便宜價格res。在遞歸函數中,首先判斷如果當前cur為目標結點dst,那么結果res賦值為out,並直接返回。你可能會納悶為啥不是取二者中較小值更新結果res,而是直接賦值呢?原因是我們之后做了剪枝處理,使得out一定會小於結果res。然后判斷如果K小於0,說明超過轉機次數了,直接返回。然后就是遍歷當前結點cur能到達的所有結點了,對於遍歷到的結點,首先判斷如果當前結點已經訪問過了,直接跳過。或者是當前價格out加上到達這個結點需要的價格之和大於結果res的話,那么直接跳過。這個剪枝能極大的提高效率,是壓線過OJ的首要功臣。之后就是標記結點訪問,調用遞歸函數,以及還原結點狀態的常規操作了,參見代碼如下:
解法一:
class Solution { public: int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) { int res = INT_MAX; unordered_map<int, vector<vector<int>>> m; unordered_set<int> visited{{src}}; for (auto flight : flights) { m[flight[0]].push_back({flight[1], flight[2]}); } helper(m, src, dst, K, visited, 0, res); return (res == INT_MAX) ? -1 : res; } void helper(unordered_map<int, vector<vector<int>>>& m, int cur, int dst, int K, unordered_set<int>& visited, int out, int& res) { if (cur == dst) {res = out; return;} if (K < 0) return; for (auto a : m[cur]) { if (visited.count(a[0]) || out + a[1] > res) continue; visited.insert(a[0]); helper(m, a[0], dst, K - 1, visited, out + a[1], res); visited.erase(a[0]); } } };
下面這種解法是用BFS來做的,還是來遍歷圖,不過這次是一層一層的遍歷,需要使用queue來輔助。前面建立圖的數據結構的操作和之前相同,BFS的寫法還是經典的寫法,但需要注意的是這里也同樣的做了剪枝優化,當當前價格加上新到達位置的價格之和大於結果res的話直接跳過。最后注意如果超過了轉機次數就直接break,參見代碼如下:
解法二:
class Solution { public: int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) { int res = INT_MAX, cnt = 0; unordered_map<int, vector<vector<int>>> m; queue<vector<int>> q{{{src, 0}}}; for (auto flight : flights) { m[flight[0]].push_back({flight[1], flight[2]}); } while (!q.empty()) { for (int i = q.size(); i > 0; --i) { auto t = q.front(); q.pop(); if (t[0] == dst) res = min(res, t[1]); for (auto a : m[t[0]]) { if (t[1] + a[1] > res) continue; q.push({a[0], t[1] + a[1]}); } } if (cnt++ > K) break; } return (res == INT_MAX) ? -1 : res; } };
再來看使用Bellman Ford算法的解法,關於此算法的detail可以上網搜帖子看看。核心思想還是用的動態規划Dynamic Programming,最核心的部分就是松弛操作Relaxation,也就是DP的狀態轉移方程。這里我們使用一個二維DP數組,其中dp[i][j]表示最多飛i次航班到達j位置時的最少價格,那么dp[0][src]初始化為0,因為飛0次航班的價格都為0,轉機K次,其實就是飛K+1次航班,我們開始遍歷,i從1到K+1,每次dp[i][src]都初始化為0,因為在起點的價格也為0,然后即使遍歷所有的航班x,更新dp[i][x[1]],表示最多飛i次航班到達航班x的目的地的最低價格,用最多飛i-1次航班,到達航班x的起點的價格加上航班x的價格之和,二者中取較小值更新即可,參見代碼如下:
解法三:
class Solution { public: int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) { vector<vector<int>> dp(K + 2, vector<int>(n, 1e9)); dp[0][src] = 0; for (int i = 1; i <= K + 1; ++i) { dp[i][src] = 0; for (auto x : flights) { dp[i][x[1]] = min(dp[i][x[1]], dp[i - 1][x[0]] + x[2]); } } return (dp[K + 1][dst] >= 1e9) ? -1 : dp[K + 1][dst]; } };
我們可以稍稍優化下上面解法的空間復雜度,使用一個一維的DP數組即可,具體思路沒有啥太大的區別,參見代碼如下:
解法四:
class Solution { public: int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) { vector<int> dp(n, 1e9); dp[src] = 0; for (int i = 0; i <= K; ++i) { vector<int> t = dp; for (auto x : flights) { t[x[1]] = min(t[x[1]], dp[x[0]] + x[2]); } dp = t; } return (dp[dst] >= 1e9) ? -1 : dp[dst]; } };
類似題目:
參考資料:
https://leetcode.com/problems/cheapest-flights-within-k-stops/discuss/115596/c++-8-line-bellman-ford