《代码随想录》Ⅶ 回溯算法 332.重新安排行程

《代码随想录》Ⅶ 回溯算法 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)​。

白展堂:人生就是这样,苦和累你总得选一样吧?哪有什么好事都让你一个人占了呢。 ——《武林外传》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值