332.重新安排行程
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例 1:
输入: tickets = [[“MUC”,“LHR”],[“JFK”,“MUC”],[“SFO”,“SJC”],[“LHR”,“SFO”]]
输出:[“JFK”,“MUC”,“LHR”,“SFO”,“SJC”]
示例 2:
输入: tickets = [[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]
输出:[“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]
解释: 另一种有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”] ,但是它字典排序更大更靠后。
提示:
- 1 ≤ t i c k e t s . l e n g t h ≤ 300 1 \leq tickets.length \leq 300 1≤tickets.length≤300
- t i c k e t s [ i ] . l e n g t h = = 2 tickets[i].length == 2 tickets[i].length==2
- f r o m i . l e n g t h = = 3 fromi.length == 3 fromi.length==3
- t o i . l e n g t h = = 3 toi.length == 3 toi.length==3
fromi
和toi
由大写英文字母组成fromi != toi
解法一(回溯)
思路分析:
- 对于该问题进行分析,即我们需要从起点去寻找路径,找到一个节点后,若该节点符合则可以继续查找,若该节点不符合,则需要回溯删除该节点,换节点继续搜索。
- 即解决该题,要考虑回溯,则使用回溯的思路来解决。
- 考虑回溯函数的三要素:
- 即回溯函数的返回值和参数;对于该题,我们需要确定选择某个节点后,可以找到符合题意(使用所有机票,并字典排序返回最小)的行程,所以当可以找到时,返回
true
,如此便可确定选择该节点,可以找到行程;当无法找到时,再进行回溯;对于函数的参数,可以当需要时进行添加。 - 思考回溯的结束条件,假设符合题意的路径存储在
List<String> ans
中,则当ans.size() == tickets.size()+1
时,说明找到路径,返回true
- 考虑回溯的遍历过程,即根据上一层递归确定的某机票的终点,即该层递归所依据的终点,来查找对应的机票,并选择对应的节点加入路径中,然后继续递归并判断是否为符合题意得节点,若符合,则结束递归,若不符合,则回溯并更换节点。
- 即回溯函数的返回值和参数;对于该题,我们需要确定选择某个节点后,可以找到符合题意(使用所有机票,并字典排序返回最小)的行程,所以当可以找到时,返回
- 对于根据机票起点,查找对应字典序最小得终点;使用
for
循环遍历整个tickets
机票列表,容易超时。 - 所以可以再寻找行程前,对整个机票做一个映射关系,即创建一个
Map<String, Map<String, Integer>> reflects
;则可以通过起点字符串,快速得到其对应得所有终点reflects.get(from)
;即Map<String, Integer>
中,Integer
用于记录对应终点得数目,每到达一次对应终点,则其数目减一,如此可以避免重复使用机票。 - 同时因为题目要求所得行程为字典排序最小的行程,所以
Map<String, Integer> reflect = TreeMap()
,即TreeMap()
使对应得终点机票按照key
,即字符串字典序进行排序。 - 如此,回溯函数,选取终点节点时,是从字典排序最小得终点开始选择,这样便可以保证,函数获取到的第一条行程,便是最符合题意得行程。
实现代码如下:
class Solution {
LinkedList<String> ans = new LinkedList<>();
Map<String, Map<String, Integer>> reflects = new HashMap<>();
public List<String> findItinerary(List<List<String>> tickets) {
// 建立机票之间起点到多个终点的映射
for (List<String> ticket : tickets) {
Map<String, Integer> reflect; // 终点Map
if (reflects.containsKey(ticket.get(0))) {
reflect = reflects.get(ticket.get(0)); // 获取已存在的起点-终点映射
reflect.put(ticket.get(1), reflect.getOrDefault(ticket.get(1), 0) + 1);
} else {
reflect = new TreeMap<>(); // 升序Map
reflect.put(ticket.get(1), 1); // 保存对应的终点及数目
}
reflects.put(ticket.get(0), reflect); // 更新 起点-终点的映射
}
ans.add("JFK"); // 添加行程起点
backtracking(tickets.size());
return ans;
}
private boolean backtracking(int ticketNumber) {
if (ans.size() == ticketNumber + 1)
return true; // 找到符合题意的行程
String from = ans.getLast();
if (reflects.containsKey(from)) {
for (Map.Entry<String, Integer> ticket : reflects.get(from).entrySet()) {
int count = ticket.getValue(); // 获取该起点对应的终点的数目
if (count > 0) { // 说明存在机票
ans.add(ticket.getKey()); // 将对应终点添加到行程
ticket.setValue(count-1); // 修改机票数目
if (backtracking(ticketNumber))
return true; // 找到符合题意的行程后 返回
ans.removeLast(); // 回溯
ticket.setValue(count);
}
}
}
return false;
}
}
提交结果如下:
解答成功:
执行耗时:9 ms,击败了55.67% 的Java用户
内存消耗:44.4 MB,击败了13.22% 的Java用户
复杂度分析:
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)