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;
    }
};


相关文章
|
13天前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
27 6
|
13天前
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
28 4
|
13天前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
42 2
|
13天前
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
16 7
|
13天前
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
13 4
|
14天前
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
32 5
|
13天前
|
算法 Python
【Leetcode刷题Python】 LeetCode 2038. 如果相邻两个颜色均相同则删除当前颜色
本文介绍了LeetCode 2038题的解法,题目要求在一个由'A'和'B'组成的字符串中,按照特定规则轮流删除颜色片段,判断Alice是否能够获胜,并提供了Python的实现代码。
25 3
|
13天前
|
算法 Python
【Leetcode刷题Python】剑指 Offer 33. 二叉搜索树的后序遍历序列
本文提供了一种Python算法,用以判断给定整数数组是否为某二叉搜索树的后序遍历结果,通过识别根节点并递归验证左右子树的值是否满足二叉搜索树的性质。
11 3
|
13天前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - II. 从上到下打印二叉树 II
本文提供了一种Python实现方法,用于层次遍历二叉树并按层打印结果,每层节点按从左到右的顺序排列,每层打印到一行。
24 3
|
14天前
|
Python
【Leetcode刷题Python】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode题目"剑指 Offer 21. 调整数组顺序使奇数位于偶数前面"的两种Python解决方案,一种是使用双端队列调整数组顺序,另一种是使用双指针法将奇数移到数组前半部分,偶数移到后半部分。
15 4