《代码随想录》Ⅶ 回溯算法 332.重新安排行程
努力学习!
题目:力扣链接
-
给你一份航线列表
tickets ,其中tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从
JFK(肯尼迪国际机场)出发的先生,所以该行程必须从JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。- 例如,行程
["JFK", "LGA"] 与["JFK", "LGB"] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
- 例如,行程
一、思想
这道题的核心思想是使用回溯算法,通过深度优先搜索(DFS)来遍历所有可能的行程路径。由于题目要求按字典序返回最小的行程组合,因此在选择下一个目的地时,我们需要优先选择字典序较小的机场。为了实现这一点,我们使用了一个嵌套的数据结构:unordered_map<string, map<string, int>>,其中外层unordered_map的键是出发机场,内层map的键是到达机场,值是该航班的剩余次数。通过这种方式,我们可以确保在遍历时按字典序选择下一个目的地。
二、代码
class Solution
{
public:
// 使用嵌套的unordered_map和map来存储航班信息
// 外层unordered_map: key为出发机场,value为map
// 内层map: key为到达机场,value为航班次数(支持多张相同机票)
unordered_map<string, map<string, int>> targets;
// 回溯函数,用于寻找有效的行程路径
// res: 当前路径结果
// num: 总机票数量
bool backtracking(vector<string> &res, int num)
{
// 如果当前路径长度等于机票数量+1,说明找到有效路径
if (num + 1 == res.size()) {
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(res, num))
return true;
// 回溯:恢复状态
target.second++;
res.pop_back();
}
}
return false;
}
// 主函数,寻找行程安排
vector<string> findItinerary(vector<vector<string>> &tickets)
{
targets.clear(); // 清空航班信息
vector<string> res; // 存储最终结果
// 初始化航班信息
for (const vector<string> &vec : tickets) {
targets[vec[0]][vec[1]]++; // 记录出发机场到到达机场的航班次数
}
// 从JFK机场开始
res.push_back("JFK");
// 开始回溯搜索
backtracking(res, tickets.size());
return res;
}
};
三、代码解析
1. 算法工作原理分解
1.1 回溯函数 backtracking
-
目的:递归地尝试从当前机场出发,选择下一个目的地,并构建有效的行程路径。
-
实现:
-
终止条件:如果当前路径的长度等于机票数量加1,说明已经找到了一条有效的行程路径,返回
true。 -
遍历目的地:从当前机场出发,遍历所有可能的目的地。由于内层使用了
map,遍历时会自动按字典序进行。 -
选择与回溯:
- 如果某个目的地还有剩余的航班次数,则选择该目的地,将其加入路径,并减少航班次数。
- 递归调用
backtracking函数,继续尝试构建后续的行程路径。 - 如果递归返回
true,说明找到了有效的路径,直接返回true。 - 如果递归返回
false,则回溯,撤销当前选择,恢复航班次数,并尝试下一个目的地。
-
返回
false:如果所有目的地都无法构建有效路径,则返回false。
-
1.2 主函数 findItinerary
-
目的:初始化航班信息,并调用回溯函数寻找有效的行程路径。
-
实现:
- 初始化航班信息:遍历所有机票,记录每个出发机场到到达机场的航班次数。
- 从JFK机场开始:将
"JFK"加入路径,作为行程的起点。 - 调用回溯函数:调用
backtracking函数,开始寻找有效的行程路径。 - 返回结果:返回找到的行程路径。
2. 关键点说明
2.1 数据结构的选择
-
unordered_map<string, map<string, int>> :外层使用unordered_map是为了快速查找出发机场对应的目的地信息。内层使用map是为了在遍历时按字典序选择下一个目的地。
2.2 路径的选择与回溯
- 路径:
res变量用于存储当前的行程路径。 - 选择:每次选择一个目的地,并将其加入路径。
- 回溯:在递归调用返回后,撤销当前选择,恢复航班次数,并尝试下一个目的地。
2.3 航班次数的管理
- 航班次数:内层
map的值表示该航班的剩余次数。每次选择一个目的地时,减少航班次数;回溯时,恢复航班次数。
四、复杂度分析
-
时间复杂度:
O(n log n)- 其中
n是机票的数量。初始化航班信息时,需要对每个机票进行插入操作,插入到map中的时间复杂度为O(log n),因此总的时间复杂度为O(n log n)。 - 回溯过程中,每个机票最多被使用一次,因此回溯的时间复杂度为
O(n)。 - 综合来看,总的时间复杂度为
O(n log n)。
- 其中
-
空间复杂度:
O(n)- 存储航班信息的数据结构需要
O(n)的空间。 - 递归调用栈的深度最多为
n,因此空间复杂度为O(n)。 - 结果集
res的空间复杂度为O(n)。 - 综合来看,总的空间复杂度为
O(n)。
- 存储航班信息的数据结构需要
白展堂:人生就是这样,苦和累你总得选一样吧?哪有什么好事都让你一个人占了呢。 ——《武林外传》
573

被折叠的 条评论
为什么被折叠?



