题目:[1]
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
说明
- 如果存在多种有效的行程,你可以按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前
- 所有的机场都用三个大写字母表示(机场代码)。
- 假定所有机票至少存在一种合理的行程。
示例
- 示例 1
输入: [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
输出: ["JFK", "MUC", "LHR", "SFO", "SJC"]
- 示例 2
输入: [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出: ["JFK","ATL","JFK","SFO","ATL","SFO"]
解释: 另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"]。但是它自然排序更大更靠后。
抛砖引玉

思路
- 数组 tickets,索引:0->起点,1->终点
- 遍历数组 tickets,如果两个子集起点终点相等则说明两个子集连续,可以拼接起来
- 要求每个 tickets 的子集都需要有节点被拼接到结果数组中
- 如果同一个终点对应多个起点按照起点字母排序拼接
实现
- 遍历 tickets 生成 map,起点作为 map 的哈希: 此时涉及重复起点的问题,题目要求按照字符大小排序,则 map 的 value 存放:到达的集合
- 从指定起点开始一直查询起点对应的终点,直到无对应终点结束
在枚举路线时,开始的思路是根据起点查到其对应的终点,从前向后遍历这些可能的终点,遇到终点不能作为其他子集起点则跳过最后拼接。
这种思路实现后会发现有些情况会使 tickets 的子集部分不被拼接(及 map 的 value 不能被清空):
- 遍历 map 的 value 时存在元素不连接其他起点
- 一个终点对应多个起点时选中当前排序小的起点,时也存在后续节点不被连接的情况
枚举路线的逻辑:
- 递归到 map 的 value 为空时开始反向(从后向前填充)
- 枚举多个起点递归,只保留可以递归到 map 的 value 为空的情况
递归
终点查询的逻辑借助递归完成
- 参数:起点 start
- 终止条件:map 中无对应起点
- 递归到 map 的 value 为空时开始反向(从后向前填充)
/**
* @param {string[][]} tickets
* @return {string[]}
*/
var findItinerary = function (tickets) {
let _result = [],
len = tickets.length,
map = new Map()
for (let i = 0; i < len; i++) {
if (map.has(tickets[i][0])) {
let items = map.get(tickets[i][0])
items.push(tickets[i][1])
items.sort()
map.set(tickets[i][0], items)
} else {
map.set(tickets[i][0], [tickets[i][1]])
}
}
function getEnd(start) {
let items = map.get(start)
// 枚举 选择传入节点对应的终点
while (items && items.length) {
getEnd(items.shift())
}
// 优先从map-value遍历完成的节点存放
_result.unshift(start)
}
getEnd('JFK')
return _result
}
- 枚举多个起点递归,只保留可以递归到 map 的 value 为空的情况
/**
* @param {string[][]} tickets
* @return {string[]}
*/
var findItinerary = function (tickets) {
let _result = ['JFK'],
len = tickets.length,
map = new Map()
for (let i = 0; i < len; i++) {
if (map.has(tickets[i][0])) {
let items = map.get(tickets[i][0])
items.push(tickets[i][1])
items.sort()
map.set(tickets[i][0], items)
} else {
map.set(tickets[i][0], [tickets[i][1]])
}
}
function getEnd(start, step) {
// 满足能每个子元素都拼接到
if (step === len) return true
let items = map.get(start) || []
// 不存在与当前起点连接的终点
if (items.length === 0) return false
for (let i = 0; i < items.length; i++) {
let item = items[i]
// 一个起点对应多个终点时,如果选择当前起点
items.splice(i, 1)
_result.push(item)
// 检查是否能 - 满足能每个子元素都拼接到
if (getEnd(item, step + 1)) {
return true
} else {
// 如果不满足此时还不能选择这个点与当前起点连接
items.splice(i, 0, item)
_result.pop()
}
}
}
getEnd('JFK', 0)
return _result
}
博客: 小书童博客[2]
每天的每日一题,写的题解会同步更新到公众号一天一大 lee 栏目 欢迎关注留言
公号: 坑人的小书童

参考资料
[1]题目:: https://leetcode-cn.com/problems/reconstruct-itinerary/
[2]小书童博客: http://gaowenju.com/