The nice thing about DFS is it tries a path, and if that’s wrong (i.e. path does not lead to solution), DFS goes one step back and tries another path. It continues to do so until we’ve found the correct path (which leads to the solution). You need to always bear this nice feature in mind when utilizing DFS to solve problems.
In this problem, the path we are going to find is an itinerary which:
1.uses all tickets to travel among airports2.preferably in ascending lexical order of airport code Keep in mind that requirement
1 must be satisfied before we consider
2.If we always choose the airport with the smallest lexical order, this would lead to a perfectly lexical-ordered itinerary, but pay attention that when doing so, there can be a “dead end” somewhere in the tickets such that we are not able visit all airports (or we can’t use all our tickets), which is bad because it fails to satisfy requirement 1 of this problem. Thus we need to take a step back and try other possible airports, which might not give us a perfectly ordered solution, but will use all tickets and cover all airports.
Thus it’s natural to think about the “backtracking” feature of DFS. We start by building a graph and then sorting vertices in the adjacency list so that when we traverse the graph later, we can guarantee the lexical order of the itinerary can be as good as possible. When we have generated an itinerary, we check if we have used all our airline tickets. If not, we revert the change and try another ticket. We keep trying until we have used all our tickets.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57public class Solution {
private HashMap<String, List<String>> adjList = new HashMap<>();
private LinkedList<String> route = new LinkedList<>();
private int numTickets = 0;
private int numTicketsUsed = 0;
public List<String> findItinerary(String[][] tickets) {
if (tickets == null || tickets.length == 0) return route;
// build graph
numTickets = tickets.length;
for (int i = 0; i < tickets.length; ++i) {
if (!adjList.containsKey(tickets[i][0])) {
// create a new list
List<String> list = new ArrayList<>();
list.add(tickets[i][1]);
adjList.put(tickets[i][0], list);
} else {
// add to existing list
adjList.get(tickets[i][0]).add(tickets[i][1]);
}
}
// sort vertices in the adjacency list so they appear in lexical order
for (Map.Entry<String, List<String>> entry : adjList.entrySet()) {
Collections.sort(entry.getValue());
}
// start DFS
route.add("JFK");
dfsRoute("JFK");
return route;
}
private void dfsRoute(String v) {
// base case: vertex v is not in adjacency list
// v is not a starting point in any itinerary, or we would have stored it
// thus we have reached end point in our DFS
if (!adjList.containsKey(v)) return;
List<String> list = adjList.get(v);
for (int i = 0; i < list.size(); ++i) {
String neighbor = list.get(i);
// remove ticket(route) from graph
list.remove(i);
route.add(neighbor);
numTicketsUsed++;
dfsRoute(neighbor);
// we only return when we have used all tickets
if (numTickets == numTicketsUsed) return;
// otherwise we need to revert the changes and try other tickets
list.add(i, neighbor);
// This line took me a long time to debug
// we must remove the last airport, since in an itinerary, the same airport can appear many times!!
route.removeLast();
numTicketsUsed--;
}
}
}