PracHub
QuestionsPremiumCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates graph algorithms and pathfinding competencies, multi-criteria optimization and weighting, data structure selection, heuristic design, handling dynamic updates and closures, and correctness and edge-case reasoning for route planning.

  • Medium
  • Stripe
  • Coding & Algorithms
  • Software Engineer

Plan bicycle routes on a city map

Company: Stripe

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: Medium

Interview Round: Technical Screen

You are given a city bicycle network as a weighted graph where edges encode distance, bike-lane availability, elevation gain, and traffic risk. Compute the bicycle route that minimizes a composite cost (e.g., time penalized by risk and elevation), subject to constraints such as avoiding roads without bike lanes or honoring temporary closures. Support alternative routes (top K), dynamic updates when a road closes, and turn-by-turn directions. Describe the data structures, the algorithmic choices (e.g., Dijkstra, A*, multi-criteria search), heuristics, tie-breaking, and the complexity. Include how you would validate correctness and handle edge cases like disconnected components.

Quick Answer: This question evaluates graph algorithms and pathfinding competencies, multi-criteria optimization and weighting, data structure selection, heuristic design, handling dynamic updates and closures, and correctness and edge-case reasoning for route planning.

# Plan Bicycle Routes on a City Map Build a bicycle route planner over a city map modeled as an **undirected, weighted graph**, and return the *K* cheapest simple routes between two intersections. Implement: ```python def solution(roads, start, end, k, require_bike_lanes, closed_roads): ``` ## Inputs - **`roads`** — a list of roads. Each road is a 6-element tuple `(u, v, distance, bike_lane, elevation_gain, traffic_risk)`: - `u`, `v` — the two intersection ids the road connects (the road is **undirected**). - `distance` — a positive integer (`1 <= distance <= 1000`). - `elevation_gain`, `traffic_risk` — integers in the range `0..100`. - `bike_lane` — `1` if the road has a bike lane, `0` otherwise. - **`start`**, **`end`** — the source and destination intersection ids. - **`k`** — the maximum number of routes to return. - **`require_bike_lanes`** — a boolean. If `True`, only roads with `bike_lane == 1` may be used. - **`closed_roads`** — a list of `(u, v)` pairs marking roads that are temporarily closed. These pairs are **undirected**: `(u, v)` and `(v, u)` refer to the same road. Re-calling `solution` with a different `closed_roads` models dynamic map updates. ## Composite cost The bicycle cost of a single road is: ``` cost = distance + 2 * elevation_gain + 3 * traffic_risk ``` The **total cost** of a route is the sum of the costs of the roads along it. ## What to do 1. **Filter the graph.** Drop every road that appears in `closed_roads`. Then, if `require_bike_lanes` is `True`, also drop every road with `bike_lane == 0`. Build the undirected graph from the remaining roads. 2. **Find routes.** Search for **simple** routes from `start` to `end` (a simple route visits each intersection at most once — no repeated intersections). 3. **Rank and trim.** Sort the routes by **total cost ascending**. On a tie, the route whose intersection list is **lexicographically smaller** comes first. Return the first `k` routes. If fewer than `k` feasible routes exist, return all of them. ## Output format Return a list of routes, where **each route is a pair `(total_cost, path)`**: - `total_cost` — the route's total composite cost (an integer). - `path` — the list of intersection ids visited in order, starting with `start` and ending with `end`. The returned list is already sorted by the ranking rule above. ## Edge cases - **`start == end`:** return `[(0, [start])]` immediately (a single zero-cost route consisting of just that intersection), regardless of the roads given — even when `roads` is empty. - **No route exists** (e.g. `start` or `end` has no usable roads after filtering, or `start` and `end` are disconnected once closed / non-bike-lane roads are removed): return `[]`. ## Notes - Roads are undirected, and there is at most one road between any pair of intersections. - Use an **exact** shortest-path / K-shortest-paths search. No coordinate or distance heuristic is provided, so A\*-style estimation is not applicable here.

Constraints

  • 1 <= n <= 100
  • 0 <= len(roads) <= 500
  • 1 <= distance <= 1000
  • 0 <= elevation_gain <= 100
  • 0 <= traffic_risk <= 100
  • bike_lane is either 0 or 1
  • 1 <= k <= 10
  • Roads are undirected
  • There is at most one undirected road between any pair of intersections

Examples

Input: ([(0, 1, 5, 1, 0, 0), (1, 4, 7, 1, 0, 0), (0, 2, 6, 1, 0, 0), (2, 4, 6, 1, 0, 0), (0, 3, 4, 1, 0, 0), (3, 4, 5, 1, 0, 0), (1, 2, 2, 1, 0, 0), (0, 4, 20, 1, 0, 0)], 0, 4, 3, False, [])

Expected Output: [(9, [0, 3, 4]), (12, [0, 1, 4]), (12, [0, 2, 4])]

Explanation: No roads are filtered out. The cheapest route is 0->3->4 with cost 4+5=9. The next two routes both cost 12, so [0, 1, 4] comes before [0, 2, 4] lexicographically.

Input: ([(0, 1, 1, 0, 0, 0), (1, 3, 1, 1, 0, 0), (0, 2, 2, 1, 0, 0), (2, 3, 2, 1, 0, 0), (0, 3, 10, 1, 0, 0)], 0, 3, 3, True, [(3, 0)])

Expected Output: [(4, [0, 2, 3])]

Explanation: Road 0-3 is closed even though it is listed as (3, 0), and road 0-1 is removed because it has no bike lane. Only 0->2->3 remains, with total cost 2+2=4.

Input: ([(0, 1, 1, 1, 2, 0), (1, 3, 1, 1, 0, 1), (0, 2, 2, 1, 0, 0), (2, 3, 3, 1, 1, 0), (0, 3, 8, 1, 0, 0)], 0, 3, 3, False, [])

Expected Output: [(7, [0, 2, 3]), (8, [0, 3]), (9, [0, 1, 3])]

Explanation: Composite costs matter: 0->2->3 costs 2 + (3 + 2*1) = 7, direct 0->3 costs 8, and 0->1->3 costs (1 + 2*2) + (1 + 3*1) = 9.

Input: ([(0, 1, 2, 1, 0, 0), (1, 2, 2, 1, 0, 0), (2, 3, 2, 0, 0, 0)], 0, 3, 2, True, [])

Expected Output: []

Explanation: Requiring bike lanes removes road 2-3, so the destination becomes unreachable after filtering.

Input: ([], 5, 5, 4, False, [])

Expected Output: [(0, [5])]

Explanation: When start equals end, the zero-length simple route is valid even if there are no roads.

Solution

def solution(roads, start, end, k, require_bike_lanes, closed_roads):
    import heapq
    from collections import defaultdict

    if k <= 0:
        return []
    if start == end:
        return [(0, [start])]

    def normalize(a, b):
        return (a, b) if a <= b else (b, a)

    closed = {normalize(u, v) for u, v in closed_roads}
    graph = defaultdict(list)

    for u, v, distance, bike_lane, elevation_gain, traffic_risk in roads:
        if normalize(u, v) in closed:
            continue
        if require_bike_lanes and bike_lane == 0:
            continue

        cost = distance + 2 * elevation_gain + 3 * traffic_risk
        graph[u].append((v, cost))
        graph[v].append((u, cost))

    if start not in graph or end not in graph:
        return []

    for node in graph:
        graph[node].sort(key=lambda item: item[0])

    heap = [(0, (start,), start)]
    routes = []

    while heap and len(routes) < k:
        total_cost, path, node = heapq.heappop(heap)

        if node == end:
            routes.append((total_cost, list(path)))
            continue

        visited = set(path)
        for neighbor, edge_cost in graph[node]:
            if neighbor in visited:
                continue
            heapq.heappush(heap, (total_cost + edge_cost, path + (neighbor,), neighbor))

    return routes
Explanation
This finds up to **K shortest *simple* routes** (no repeated intersection) using a best-first search over partial paths — a lazy variant of K-shortest-paths. **Build the graph.** Closed roads are normalized with `normalize(a,b)` (smaller id first) into a set so undirected lookups match regardless of orientation. Each road is added only if it isn't closed and — when `require_bike_lanes` is set — has `bike_lane == 1`. Its weight is the composite cost `distance + 2*elevation_gain + 3*traffic_risk`, added in both directions since roads are undirected. **Two early exits.** `start == end` returns `[(0, [start])]` immediately (covers the empty-graph self-loop test). If after filtering `start` or `end` has no incident edges, the graph is disconnected for them, so return `[]`. **The search.** Each adjacency list is sorted by neighbor id, and a min-heap holds `(total_cost, path_tuple, node)`. Heap order is the trick that gives the required sort: Python compares `total_cost` first, then — on ties — the `path` tuple lexicographically, exactly the tie-break the problem asks for. We pop the cheapest partial path; if its `node` is `end`, it's the next-best complete route, so record it. Otherwise expand to each neighbor not already in `set(path)` (keeps paths *simple*) and push the extended path. **Why it's correct.** Popping in non-decreasing `(cost, path)` order means the i-th completed path is genuinely the i-th best, so the first K completions are the answer, already sorted. Stopping once `len(routes) == k` avoids enumerating the full exponential path space.

Time complexity: O(P log P) where P is the number of partial simple paths popped/pushed; P is worst-case exponential in n (up to O(n!) distinct simple paths), but the search stops after K completions, so in practice it expands only the cheapest fringe. Each heap op also pays O(L) to copy a length-L path tuple, giving O(P * L * log P) more precisely.. Space complexity: O(P * L) worst case: the heap can hold up to P partial paths, each stored as a tuple of up to L = O(n) intersections. The graph itself is O(V + E)..

Hints

  1. First build a filtered adjacency list that removes closed roads and, when required, roads without bike lanes.
  2. Find the best route with Dijkstra, then generate the next best simple routes by deviating from previously accepted paths (Yen's algorithm).
Last updated: May 13, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Assign Reviewers from Changed Files - Stripe (medium)
  • Generate Account Email Notifications - Stripe (medium)
  • Calculate Transaction Fees - Stripe (medium)
  • Build an Account Transfer Ledger - Stripe (medium)
  • Implement Validation and String Compression - Stripe (hard)