把每个机场视为一个节点,一张机票视为连接两个机场节点的有向边,这道题实际上是求一个有向图的一笔画问题,即从一个确定节点开始,通过所有边一次且仅一次。
除此之外,还有两点要注意:
所给测例不会有无解的情况(可知图必然是连通的,且除了起点和终点以外,所有节点的入边和出边数目相等)。
如果有多个解,需要给出按字母顺序最小的那个解。
既然是一个图论问题,首先要做的是,遍历所有的机票,以邻接表的形式建起这个图。接下来的问题是如何在这个邻接表的基础上给出一笔画的字母最小解。我们从起点开始,随机抓取这个起点对应的某一条边作为下一跳,然后在图中删除这条边,再以这条边对应的邻接点为起点重复这一过程,直到当前起点已无出边为止,这样我们可以得到一条路径。但问题在于这条路径并不能覆盖所有的边。
不过可以证明,图中剩下的边所组成的子图里,我们从任意一个点开始,再按照上面的过程走一遍,一定能够回到这个起点。因此,我们可以不断重复上述过程,得到一个又一个的环路。每得到一个这样的环路,我们就将其插入到我们上一步所得到的路径里去。
通过上述方法,我们可以得到一个可行解,但这个可行解并不是最小的。为了得到最小可行解,还有两个工作要做:
每一次选下一跳时,不要随机选择,而要选择字母序最小的邻接点。所以我们要求邻接表中,每个节点的邻接节点列表是有序的。
当已经进行一次迭代得到一个路径后,下一步迭代的起点选择,需要在这个路径尽可能靠后的位置。
按照上面的方式去做,可以通过所有的测例。我们来分析一下复杂度。n代表机场数,m代表机票数。
空间复杂度,是一个邻接表。所以是O(n + m)。
时间复杂度,建邻接表的复杂度是O(m),让邻接表有序化的复杂度不大于O(nlgn),然后执行一笔画的复杂度是O(m)。因此总的复杂度不大于O(m + nlgn)。
public class Solution {
public List<String> findItinerary(String[][] tickets) {
List<String> result = new ArrayList<String>();
if (tickets.length == 0) {
return result;
}
Map<String, List<String>> graph = new HashMap<String, List<String>>();
for (int i = 0; i < tickets.length; i++) {
if (graph.get(tickets[i][0]) == null) {
graph.put(tickets[i][0], new ArrayList<String>());
}
List<String> list = graph.get(tickets[i][0]);
list.add(tickets[i][1]);
if (graph.get(tickets[i][1]) == null) {
graph.put(tickets[i][1], new ArrayList<String>());
}
}
for (Map.Entry<String, List<String>> entry: graph.entrySet()) {
Collections.sort(entry.getValue(), Collections.reverseOrder());
}
result.add("JFK");
List<String> list = graph.get("JFK");
int index = 1;
while (tickets.length + 1 > result.size()) {
List<String> tmp = new ArrayList<String>();
while(list.size() > 0) {
String next = list.get(list.size() - 1);
tmp.add(next);
list.remove(list.size() - 1);
list = graph.get(next);
}
result.addAll(index, tmp);
for (int i = result.size() -1; i >= 0; i--) {
if (graph.get(result.get(i)).size() > 0) {
list = graph.get(result.get(i));
index = i + 1;
break;
}
}
}
return result;
}
}