leetcode332重新安排行程刷题打卡

332. 重新安排行程

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

  • 例如,行程 ["JFK", "LGA"]["JFK", "LGB"] 相比就更小,排序更靠前。

假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

示例 1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAvb2OKG-1666503813912)(image\重新安排行程.png)]

输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]

示例 2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZINMvady-1666503813913)(image\重新安排行程2.png)]

输入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。

提示:

  • 1 <= tickets.length <= 300
  • tickets[i].length == 2
  • fromi.length == 3
  • toi.length == 3
  • fromitoi 由大写英文字母组成
  • fromi != toi

题解思路:

存放结果的数据结构

vector<string> result;

起始地与目的地的映射关系使用一个如下的数据结构来保存

unordered_map<string, map<string, int> > nevigates;
  • 起始节点一样的情况下,使用map对目的地进行排序,这样就自然的对答案进行按字典排序自然的就是最小,并增加一个引用计数,为0则表示该票不能再使用。

  • 构造对应关系

    for(vector<string> &s : tickets){
        navigates[s[0]][s[1]]++;
    }
    

    tickets是一个存放string的二维数组,其中每个元素都是一个存放string的数组,所以以这种形式的增强for循环来遍历是可行的,引用与否都可AC,但是引用可以提升效率,减少了一些拷贝留下的低效。

    循环体中的写法说明

    map<int, int> amap;
    amap[0]++; // 构造了一个key-value对 它是<0, 1>对
    
    // 本题中 s[0]就是起始地 s[1]就是目的地
    navigates[s[0]][s[1]]++; // 执行后构造了一个键值对,它是<s[0], <s[1], 1> >
    

《回溯》

如下是该题的回溯部分,我将一点一点的解释

for(pair<const string, int>& navigate : navigates[result[result.size() - 1]]){
    if(navigate.second > 0){
    result.push_back(navigate.first);
    navigate.second--;
    if(backtracking(ticketsNum, index + 1)) return true;
    navigate.second++;
    result.pop_back();
	}
}
  • 增强for循环的条件体

    • pair<const string, int>& navigate : navigates[result[result.size() - 1]]
      • result[result.size() - 1]:该表达式的结果是result的最后一个string,效果等同于result.back()
    • 利用pair<string, int>& navigate来接收navigatesvalue,此后navigate.front就是目的地,navigate.second就是引用次数
  • 循环体

    • 首先会判断引用次数是否大于0,大于0就正常进入回溯部分,否则不执行,这避免了重复对一张票子引用

    • 对进入回溯函数之前想result中加入当前票的目的地,引用次数减1,相当于标记了使用一次该票

    • 在参数列表中有一个index的参数,记录result数组中的元素个数,在终止条件中会有用的,每次进入回溯会对index + 1,记录result中的个数

构造回溯抽象树

前置信息:题目给出,本题的最开始起始地总是[JKF],所以直接在result数组中push一个"JKF"即可

根据以上条件可以判断出树的根节点以"JKF"为起始地点的票子,进入回溯就是以"JKF"为起始地选择目的地,由于使用的map来保存目的地,所以会以字典序从小到大自动排序,外层for循环的遍历顺序也就是以字典序大小来遍历,我以以下输入样例为例来构造回溯抽象树

[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]

对应的图为:
请添加图片描述

画出回溯抽象树的部分

请添加图片描述

题解路径寻找的抽象图

请添加图片描述

递归三步

  • 确定参数列表和返回值

    • 本题不用将整个树遍历完,只要找到了符合的一条树枝,直接返回结果即可,虽然一般的回溯算法不需要返回值,但该题需要一个bool返回值来遇见某一符合条件的树枝就返回,由于map自动排序,所以返回的树枝一定是字典序最小的
    • 需要一个index参数和票的大小参数ticketsNum,都是用于终止条件的
  • 确定终止条件

    • 根据题目的意思,需要把每一张票都用完,所以result数组的大小最后一定是票子的数量+1,所以终止条件就是

      index == ticketsNum + 1,无需有判断答案是最小字典序,根据前面的说法,map已经自动排序,所以第一个返回的题解一定是最小字典序的

  • 确定本层逻辑

    • 根据题意:result数组的第一个元素一定是"JKF",从JKF开始回溯,选择某一目的地后将其加入result数组,然后将该目的地作为新的起始地,接着回溯,具体细节看上面的构造抽象回溯树

完整代码:

class Solution {
public:
    // 保存航班信息的数据结构
    unordered_map<string, map<string, int>> navigates;
    // 第二个泛型是map,可以自动排序字典序
    vector<string> result;
    bool backtracking(int ticketsNum, int index){
        if(index == ticketsNum + 1){
            return true;
        }
        for(pair<const string, int>& navigate : navigates[result[result.size() - 1]]){  // result.size() - 1 随着result.size()的增加,其则顺其自然的遍历了后续门票起始
            if(navigate.second > 0){
                result.push_back(navigate.first);
                navigate.second--;
                if(backtracking(ticketsNum, index + 1)) return true;
                navigate.second++;
                result.pop_back();
            }
        }
        return false;
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        // 起始地与目的地的映射关系
        for(vector<string> s : tickets){
            navigates[s[0]][s[1]]++;
        }
        result.push_back("JFK");
        backtracking(tickets.size(), 1);
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值