重新安排行程
思路
直覺上來看 這道題和回溯法沒有什么關系,更像是圖論中的深度優先搜索。
實際上確實是深搜,但這是深搜中使用了回溯的例子,在查找路徑的時候,如果不回溯,怎么能查到目標路徑呢。
所以可以說本題也是回溯法。
「這道題目有幾個難點:」
1、一個行程中,如果航班處理不好容易變成一個圈,成為死循環
2、有多種解法,字母序靠前排在前面,該如何記錄映射關系呢 ?
3、使用回溯法(也可以說深搜) 的話,那么終止條件是什么呢?
4、搜索的過程中,如何遍歷一個機場所對應的所有機場。
如何理解死循環
這個例子就是說,出發機場和到達機場也會重復的,「如果在解題的過程中沒有對集合元素處理好,就會死循環?!?/p>
如何記錄映射關系
題目提示說:
如果存在多種有效的行程,請你按字符自然排序返回最小的行程組合。
例如,行程 [“JFK”, “LGA”] 與 [“JFK”, “LGB”] 相比就更小,排序更靠前。
所以一個機場映射多個機場,多個機場之間要靠字母序排列。
一個機場映射機一個機場,可以使用std::unordered_map<X,Y>。
如果讓一個機場映射多個機場的話std::unordered_map<X,xY>,多個機場之間再有順序的話,就是用std::map 或者std::multimap 或者 std::multiset。
這樣存放映射關系可以定義為 unordered_map<string, multiset> targets 或者 unordered_map<string, map<string, int>> targets。
含義如下:
unordered_map<string, multiset> targets:unordered_map<出發機場, 到達機場的集合> targets
unordered_map<string, map<string, int>> targets:unordered_map<出發機場, map<到達機場, 航班次數>> targets
這兩個結構,后者優于前者,因為如果使用unordered_map<string, multiset> targets 遍歷multiset的時候,不能刪除元素,一旦刪除元素,迭代器就失效了。
「再說一下為什么一定要增刪元素呢,正如開篇給出的圖中所示,出發機場和到達機場是會重復的,搜索的過程沒及時刪除目的機場就會死循環。」
所以搜索的過程中就是要不斷的刪multiset里的元素,那么推薦使用unordered_map<string, map<string, int>> targets。
在遍歷 unordered_map<出發機場, map<到達機場, 航班次數>> targets的過程中,「可以使用"航班次數"這個字段的數字做相應的增減,來標記到達機場是否使用過了?!?/p>
如果“航班次數”大于零,說明目的地還可以飛,如果如果“航班次數”等于零說明目的地不能飛了,而不用對集合做刪除元素或者增加元素的操作。
「相當于說我不刪,我就做一個標記!」
回溯三部曲
1、遞歸函數參數
使用unordered_map<string, map<string, int>> targets; 來記錄航班的映射關系
還需要ticketNum,表示有多少個航班(終止條件會用上)。
返回值是bool是因為我們只需要找到一個行程,就是在樹形結構中唯一的一條通向葉子節點的路線。
當然本題的targets和result都需要初始化,代碼如下:
for (const vector<string>& vec : tickets) {targets[vec[0]][vec[1]]++; // 記錄映射關系 } result.push_back("JFK"); // 起始機場tickets->[[“JFK”, “KUL”], [“JFK”, “NRT”], [“NRT”, “JFK”]
2、遞歸終止條件
拿題目中的示例為例,輸入: [[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]] ,這是有4個航班,那么只要找出一種行程,行程里的機場個數是5就可以了。
所以終止條件是:我們回溯遍歷的過程中,遇到的機場個數,如果達到了(航班數量+1),那么我們就找到了一個行程,把所有航班串在一起了。
if (result.size() == ticketNum + 1) {return true; }不需要收集結果,本題的result相當于 回溯算法:求組合總和!中的path,也就是本題的result就是記錄路徑的(就一條),在如下單層搜索的邏輯中result就添加元素了。
3、單層搜索的邏輯
回溯的過程中,如何遍歷一個機場所對應的所有機場呢?
「可以說本題既要找到一個對數據進行排序的容器,而且還要容易增刪元素,迭代器還不能失效」。
所以選擇了unordered_map<string, map<string, int>> targets 來做機場之間的映射。
for (pair<const string, int>& target : targets[result[result.size() - 1]]) {if (target.second > 0 ) { // 記錄到達機場是否飛過了result.push_back(target.first);target.second--;if (backtracking(ticketNum, result)) return true;result.pop_back();target.second++;}}可以看出 通過unordered_map<string, map<string, int>> targets里的int字段來判斷 這個集合里的機場是否使用過,這樣避免了直接去刪元素。
class Solution { public:// unordered_map<出發機場, map<到達機場, 航班次數>> targetsunordered_map<string,map<string,int>> targets;vector<string> res;//收集結果bool backtracking(vector<vector<string>>& tickets){if(res.size()==tickets.size()+1){return true;}for(pair<const string,int>& target:targets[res[res.size()-1]]){if(target.second>0){// 記錄到達機場是否飛過了res.push_back(target.first);target.second--;if(backtracking(tickets)) return true;res.pop_back();target.second++;}}return false;}vector<string> findItinerary(vector<vector<string>>& tickets) {for(const vector<string>& vec:tickets){targets[vec[0]][vec[1]]++;// 記錄映射關系}res.push_back("JFK");backtracking(tickets);return res;} };總結
- 上一篇: 来个小结
- 下一篇: 回文数的个数、杨辉三角