332. 重新安排行程

image.png
image.png

这道题是图的DFS,无论是图还是树,要找到所有路径定要回溯

题目的难点:

  1. 一个行程中,如果航班处理不好容易变成一个圈,成为死循环

image.png
如果没有对集合元素处理好,出发机场和到达机场会重复

  1. 有多种解法,字母序靠前排在前面,如何该记录映射关系 ?

一个机场映射多个机场,机场之间要靠字母序排列,一个机场映射多个机场,可以使用std::unordered_map,如果让多个机场之间再有顺序的话,就是用std::map 或者std::multimap 或者 std::multiset。
这样存放映射关系可以定义为unordered_map<string, map<string, int>> targets
含义如下:
unordered_map<string, map<string, int>> targets:unordered_map<出发机场, map<到达机场, 航班次数>> targets
出发机场和到达机场是会重复的,搜索的过程没及时删除目的机场就会死循环。
所以搜索的过程中就是要不断的删multiset里的元素,那么使用unordered_map<string, map<string, int>> targets。
在遍历unordered_map<出发机场, map<到达机场, 航班次数>> targets的过程中,可以使用”航班次数”这个字段的数字做相应的增减,来标记到达机场是否使用过了。
如果“航班次数”大于零,说明目的地还可以飞,如果如果“航班次数”等于零说明目的地不能飞了,而不用对集合做删除元素或者增加元素的操作。

  1. 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢
  2. 搜索的过程中,如何遍历一个机场所对应的所有机场。

回溯:

  1. 递归参数和返回值

在讲解映射关系的时候,已经讲过了,使用unordered_map<string, map<string, int>> targets; 来记录航班的映射关系,
定义为全局变量,减少函数参数的长度
参数里还需要ticketNum,表示有多少个航班(终止条件会用上)。

  1. // unordered_map<出发机场, map<到达机场, 航班次数>> targets
  2. unordered_map<string, map<string, int>> targets;
  3. bool backtracking(int ticketNum, vector<string>& result)

找到一个行程是有效的就要直接返回,返回值使用的bool
初始化targets和result

for (const vector<string>& vec : tickets) {
    targets[vec[0]][vec[1]]++; // 记录映射关系
}
result.push_back("JFK"); // 起始机场
  1. 终止条件

如果有四张票,那么机场个数是五就可以终止了
所以终止条件是:我们回溯遍历的过程中,遇到的机场个数,如果达到了(航班数量+1),那么我们就找到了一个行程,把所有航班串在一起了。

if (result.size() == ticketNum + 1) {
    return true;
}
  1. 单层搜索的逻辑

回溯的过程中,如何遍历一个机场所对应的所有机场呢?
这里刚刚说过,在选择映射函数的时候,不能选择unordered_map<string, multiset<string>> targets, 因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不在讨论了。
可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效
所以我选择了unordered_map> 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 {
private:
// unordered_map<出发机场, map<到达机场, 航班次数>> targets
unordered_map<string, map<string, int>> targets;
bool backtracking(int ticketNum, vector<string>& result) {
    if (result.size() == ticketNum + 1) {
        return true;
    }
    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++;
        }
    }
    return false;
}
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear();
        vector<string> result;
        for (const vector<string>& vec : tickets) {
            targets[vec[0]][vec[1]]++; // 记录映射关系
        }
        result.push_back("JFK"); // 起始机场
        backtracking(tickets.size(), result);
        return result;
    }
};