Find Shortest Paths in Road Network
Company: Palantir
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Onsite
Quick Answer: This question evaluates understanding of graph data structures and shortest-path concepts, including correct modeling of undirected edges, unweighted versus weighted pathfinding, and algorithmic time and space complexity.
Part 1: Build a Bidirectional Road Adjacency List
Constraints
- 0 <= len(roads) <= 10^4
- 0 <= distance <= 10^9
- City names are non-empty strings
- Each road connects two distinct cities
- If duplicate roads appear in the input, preserve them in the adjacency list
Examples
Input: [['A', 'B', 5], ['B', 'C', 3]]
Expected Output: {'A': [['B', 5]], 'B': [['A', 5], ['C', 3]], 'C': [['B', 3]]}
Explanation: Road A-B adds both A->B and B->A. Road B-C adds both B->C and C->B.
Input: [['Delhi', 'Mumbai', 2], ['Ahmedabad', 'Delhi', 1], ['Chennai', 'Bengaluru', 4]]
Expected Output: {'Ahmedabad': [['Delhi', 1]], 'Bengaluru': [['Chennai', 4]], 'Chennai': [['Bengaluru', 4]], 'Delhi': [['Ahmedabad', 1], ['Mumbai', 2]], 'Mumbai': [['Delhi', 2]]}
Explanation: The graph has two disconnected components, and the output is sorted lexicographically.
Input: []
Expected Output: {}
Explanation: An empty road list produces an empty adjacency list.
Input: [['A', 'B', 1], ['A', 'B', 1]]
Expected Output: {'A': [['B', 1], ['B', 1]], 'B': [['A', 1], ['A', 1]]}
Explanation: Duplicate roads are preserved, so each duplicate contributes another pair of adjacency entries.
Hints
- Each undirected road creates two adjacency entries: one for from_city and one for to_city.
- Use a dictionary of lists while building the graph, then sort once at the end for deterministic output.
Part 2: Shortest Number of Roads Between Two Cities
Constraints
- 0 <= len(roads) <= 10^5
- City names are non-empty strings
- Each road connects two distinct cities
- The graph may be disconnected
- If start == end, the answer is 0 even if the city does not appear in roads
Examples
Input: ([['A', 'B'], ['B', 'C'], ['C', 'D']], 'A', 'D')
Expected Output: 3
Explanation: The only path is A -> B -> C -> D, which uses 3 roads.
Input: ([['A', 'B'], ['A', 'C'], ['C', 'D']], 'B', 'D')
Expected Output: 3
Explanation: The shortest path is B -> A -> C -> D.
Input: ([['A', 'B'], ['C', 'D']], 'A', 'D')
Expected Output: -1
Explanation: A and D are in different connected components.
Input: ([], 'X', 'X')
Expected Output: 0
Explanation: No travel is needed when start and end are the same.
Input: ([['A', 'B']], 'B', 'A')
Expected Output: 1
Explanation: This confirms that roads must be treated as bidirectional.
Hints
- When every edge has equal weight, you want the path with the fewest edges. Think about exploring the graph level by level.
- Be sure to add every road in both directions before searching.
Part 3: Shortest Total Distance with Weighted Roads
Constraints
- 0 <= len(roads) <= 10^5
- 0 <= distance <= 10^9
- City names are non-empty strings
- Each road connects two distinct cities
- All road distances are non-negative
Examples
Input: ([['A', 'B', 4], ['A', 'C', 2], ['C', 'B', 1], ['B', 'D', 5], ['C', 'D', 8]], 'A', 'D')
Expected Output: 8
Explanation: The best route is A -> C -> B -> D with total distance 2 + 1 + 5 = 8.
Input: ([['X', 'Y', 7]], 'Y', 'X')
Expected Output: 7
Explanation: This checks that a single road works in both directions.
Input: ([['A', 'B', 0], ['B', 'C', 2], ['A', 'C', 5]], 'A', 'C')
Expected Output: 2
Explanation: The zero-weight edge makes A -> B -> C cheaper than the direct road.
Input: ([['A', 'B', 3], ['C', 'D', 4]], 'A', 'D')
Expected Output: -1
Explanation: There is no path between the two disconnected components.
Input: ([], 'Z', 'Z')
Expected Output: 0
Explanation: If start and end are the same city, the shortest distance is 0.
Hints
- A greedy strategy works here because edge weights are non-negative: repeatedly expand the city with the smallest known tentative distance.
- A min-heap is useful for efficiently retrieving the next city to process.