leetcode332重新安排行程刷题打卡

简介: 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;
    }
};


相关文章
|
3月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
58 6
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
52 4
|
4月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
120 2
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
25 1
|
3月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
4月前
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
65 7
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
54 5
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
29 4
|
4月前
|
Python
【Leetcode刷题Python】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode题目"剑指 Offer 21. 调整数组顺序使奇数位于偶数前面"的两种Python解决方案,一种是使用双端队列调整数组顺序,另一种是使用双指针法将奇数移到数组前半部分,偶数移到后半部分。
28 4