题目链接:https://leetcode-cn.com/problems/reconstruct-itinerary/
参考链接:
给定一个机票的字符串二维数组 [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"]。但是它自然排序更大更靠后。
- 使用对象存储每个地点根据机票生成的连接关系,使用map记录每个机票的数量,使用length记录使用票的数量
- 根据连接关系,先到达一个地点,使用一张机票,一直到机票使用完,返回结果
- 如果机票没有使用完几句没有可以到达的节点,则回溯,进行退票
- 最后返回结果
/**
* @param {string[][]} tickets
* @return {string[]}
*/
var findItinerary = function(tickets) {
const map = new Map()
const obj = {};
for(let [from, to] of tickets){
let tmp = obj[from] || []
tmp.push(to);
tmp.sort();
obj[from] = tmp;
let value = map.get(`${from}, ${to}`) || 0
map.set(`${from}, ${to}`, value + 1)
}
const ans = []; let length = 0;
const helper = (from) => {
// 票用完了
if(length === tickets.length){
ans.push(from);
return true
}
let toArr = obj[from];
if(!toArr || !toArr.length){
return false
}
for(let to of toArr){
// 没有票了
let value = map.get(`${from}, ${to}`)
if(value < 1) {
continue
}
// 设置为去过
ans.push(from);
map.set(`${from}, ${to}`, value - 1);
length++;
let result = helper(to);
if(result){
return true
}
// 退票
ans.pop();
length--;
map.set(`${from}, ${to}`, value);
}
return false
}
helper('JFK')
return ans
};
- 这个的原理在于,对于每个节点,有且最多只有一个分支是死胡同,否则就无法保证至少有一种合理路径,所以我们可以对每个节点的所有边进行深度遍历,走完每个节点的边。将这个节点放到数组中,而最后一个节点一定是死胡同的节点,因为不是死胡同节点的话,他最终还会走回这个节点。
- 从起点开始进行深度优先遍历
- 遍历完每个节点之后,删除这条链接
- 遍历完所有的路径之后,将节点放入结果中
/**
* @param {string[][]} tickets
* @return {string[]}
*/
var findItinerary = function(tickets) {
const graph = getGraph(tickets);
const ans = []
dfs(graph,'JFK', ans)
//深度优先遍历是先进先出,所以reverse反转一下
return ans.reverse()
};
//从JFS出发,深度优先遍历
function dfs(graph, cur, ans){
if(!graph.has(cur)) return
const neighbors = graph.get(cur)
//题目要求先遍历字典序小的元素
while(neighbors.length) dfs(graph, neighbors.shift(), ans)
// 因为深度优先遍历是先进先出,所以每次遍历出发点添加到最后,意为最先出
ans.push(cur)
}
// 先存储所有机场之间的关系,哪怕tickets中没有给某个机场提供目的地记录,也要把它的目的地置为空数组
function getGraph(tickets){
const map = new Map()
//收集每一个出发点的所有目的地
for(let i = 0;i < tickets.length;i++){
const from = tickets[i][0]
const to = tickets[i][1]
if(!map.has(from)) map.set(from, [])
if(!map.has(to)) map.set(to, [])
map.get(from).push(to)
}
for(let [key, value] of map){
//字典顺序排序目的地数组
value.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)
}
return map
}