leetcode-master/problems/0332.重新安排行程.md

5.7 KiB
Raw Blame History

题目地址

第242题.有效的字母异位词

思路

举一个有重复机场的例子:

为什么要举这个例子呢,就是告诉大家,出发机场和到达机场也会重复的,如果在解题的过程中没有对集合元素处理好,就会死循环。

这道题目有几个难点:

  1. 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢
  2. 这是一个图不是一棵树,使用深搜/回溯 终止条件是什么呢?
  3. 回溯的过程中,如何遍历一个城市所对应的所有城市。

首先这道题目是使用回溯法(也可以说深搜),那么按照我总结的回溯模板来。

backtracking() {
    if (终止条件) {
        存放结果;
    }

    for (枚举同一个位置的所有可能性,可以想成节点孩子的数量) {
        递归,处理节点;
        backtracking();
        回溯,撤销处理结果
    }
}

1. 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢

一个城市映射多个城市城市之间要靠字母序排列一个城市映射多个城市可以使用std::unordered_map如果让多个城市之间再有顺序的话就是用std::map 或者 或者std::multimap 或者 std::multiset。

如果对map 和 set 的实现机制不太了解,也不清楚为什么 map、multimap就是有序的同学可以看这篇文章关于哈希表,你该了解这些!

这样存放映射关系可以定义为 unordered_map<string, multiset<string>> targets 或者 unordered_map<string, map<string, int>> targets

含义如下:

unordered_map<string, multiset<string>> targetsunordered_map<出发城市, 到达城市的集合> targets unordered_map<string, map<string, int>> targetsunordered_map<出发城市, map<到达城市, 航班次数>> targets

这两个结构,我们选择了后者,因为如果使用unordered_map<string, multiset<string>> targets 遍历multiset的时候不能删除元素一旦删除元素迭代器就失效了。而本地在回溯的过程中就是要不断的增删 multiset里的元素所以 我们使用unordered_map<string, map<string, int>> targets

在遍历 unordered_map<出发城市, map<到达城市, 航班次数>> targets的过程中,可以使用航班次数这个字段的数字 --或者++,来标记到达城市是否使用过了,而不用对集合做删除元素或者增加元素的操作。

2. 这是一个图不是一棵树,使用深搜/回溯 终止条件是什么呢?

你看有多少个航班,那题目中的示例为例,输入: "MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO" 这是有4个航班那么只要找出一种行程行程里的机场个数是5就可以了。

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

3. 回溯的过程中,如何遍历一个城市所对应的所有城市。

这里刚刚说过,在选择映射函数的时候,不能选择unordered_map<string, multiset<string>> targets 因为一旦有元素增删multiset的迭代器就会失效有一些题解使用了 例如list的迭代器使用splice来保证 迭代器不失效。

可以说既要找到一个对数据经行排序的容器,而且这个容易增删元素,迭代器还不能失效。

再说一下为什么一定要增删元素呢,正如开篇我给出的图中所示,出发机场和到达机场是会重复的,搜索的过程没及时删除元素就会死循环。

所以我选择了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, index + 1, result)) return true;
            result.pop_back();
            target.second++;
        }
    }

可以看出 通过unordered_map<string, map<string, int>> targets里的int字段来判断 这个集合使用使用完了,这样避免了 增删元素。

此时完整代码如下:

C++代码


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 ) { // 使用int字段来记录到达城市是否使用过了
            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) {
        vector<string> result;
        for (const vector<string>& vec : tickets) {
            targets[vec[0]][vec[1]]++; // 记录映射关系
        }
        result.push_back("JFK");
        backtracking(tickets.size(), result);
        return result;
    }
};

更多算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。