Lesson 74 of 164
Trace the Complete Path of a Journey
You have a set of one-way tickets, each written as (from, to), in random order. Together they form one continuous route that visits no city twice. Return the cities in the order they're visited.
Example
Input: [("NewYork", "Chicago"), ("Boston", "Texas"),
("Missouri", "NewYork"), ("Texas", "Missouri")]
Output: Boston -> Texas -> Missouri -> NewYork -> Chicagograph LR
Boston --> Texas --> Missouri --> NewYork --> ChicagoTwo observations solve the problem:
- The starting city is the only
fromcity that never appears as atocity: no ticket leads into it. - From any city, there's exactly one ticket leaving it, so the next city is determined.
Brute force: search the tickets for every step
Find the start by checking, for each ticket, whether any ticket arrives at its from city. Then repeatedly scan all tickets for the one that leaves the current city. A ticket is a String[] of length 2: {from, to}.
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> trace(String[][] tickets) {
List<String> path = new ArrayList<>();
String start = null;
for (String[] t : tickets) {
boolean hasIncoming = false;
for (String[] u : tickets) {
if (u[1].equals(t[0])) hasIncoming = true;
}
if (!hasIncoming) {
start = t[0];
break;
}
}
if (start == null) return path;
String city = start;
path.add(city);
boolean moved = true;
while (moved) {
moved = false;
for (String[] t : tickets) {
if (t[0].equals(city)) {
city = t[1];
path.add(city);
moved = true;
break;
}
}
}
return path;
}
public static void main(String[] args) {
String[][] tickets = {{"NewYork", "Chicago"}, {"Boston", "Texas"}, {"Missouri", "NewYork"}, {"Texas", "Missouri"}};
System.out.println(String.join(" -> ", trace(tickets)));
}
}Boston -> Texas -> Missouri -> NewYork -> ChicagoTime: O(n²): finding the start is O(n²), and each of the n steps scans all n tickets. Space: O(1) besides the output.
Optimal: a lookup table from each city to the next
Build two structures in one pass over the tickets:
next: Map<from, to>, so the next city is anO(1)lookup.destinations: Set<to>, so "does any ticket arrive here?" is anO(1)check.
The start is the key of next that isn't in destinations. Then follow next until a city has no outgoing ticket.
Walkthrough:
next = { NewYork: Chicago, Boston: Texas, Missouri: NewYork, Texas: Missouri }
destinations = { Chicago, Texas, NewYork, Missouri }
start = Boston (the only key not in destinations)| city | next[city] |
|---|---|
| Boston | Texas |
| Texas | Missouri |
| Missouri | NewYork |
| NewYork | Chicago |
| Chicago | none → stop |
Map.get returns null when the key is missing, and that null ends the loop.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Main {
static List<String> trace(String[][] tickets) {
Map<String, String> next = new HashMap<>();
Set<String> destinations = new HashSet<>();
for (String[] t : tickets) {
next.put(t[0], t[1]);
destinations.add(t[1]);
}
String start = null;
for (String from : next.keySet()) {
if (!destinations.contains(from)) {
start = from;
break;
}
}
List<String> path = new ArrayList<>();
if (start == null) return path; // every city has an incoming ticket: the route is a loop
for (String city = start; city != null; city = next.get(city)) {
path.add(city);
}
return path;
}
public static void main(String[] args) {
String[][] tickets = {{"NewYork", "Chicago"}, {"Boston", "Texas"}, {"Missouri", "NewYork"}, {"Texas", "Missouri"}};
System.out.println(String.join(" -> ", trace(tickets)));
System.out.println(trace(new String[][]{{"A", "B"}}));
System.out.println(trace(new String[][]{}));
}
}Boston -> Texas -> Missouri -> NewYork -> Chicago
[A, B]
[]Time: O(n). Space: O(n).
Only one key of next is missing from destinations when the tickets form one route, so the iteration order of the HashMap does not change the result.
Edge cases
- No tickets: the result is empty.
- The tickets form a loop: no start exists, and the result is empty. Without the "visits no city twice" guarantee, following
nextfrom an arbitrary city could loop forever, so check the path length againstn + 1if the input can't be trusted. - Reusable tickets, several tickets from one city (LeetCode 332, Reconstruct Itinerary): this needs a graph search (Hierholzer's algorithm), not a simple lookup.
