Quick Overview

This question evaluates understanding of graph traversal and shortest-path distance concepts along with multi-criteria ranking (distance, rating, ID) and efficient exploration of large undirected graphs to avoid revisiting nodes.

Recommend top-K movies from similarity graph

Company: Google

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Onsite

## Movie Recommendation: Top K You are building a simple movie recommendation feature. ### Input - A set of movies `0..(M-1)`. - An **undirected** similarity graph `adj`, where `adj[i]` lists movies similar to movie `i`. - An array `rating[i]` (or `score[i]`) for each movie. - A starting movie `start` that the user liked. - A set `watched` of movies the user has already watched. - An integer `K`. ### Output Return a list of up to `K` **recommended movie IDs** that the user has **not watched**. ### Ranking rules 1. Prefer movies with **smaller graph distance** (fewer similarity hops) from `start`. 2. If distances tie, prefer **higher `rating`**. 3. If still tied, break ties by **smaller movie ID**. ### Notes / constraints - The graph can be large; avoid revisiting nodes. - If fewer than `K` unwatched movies are reachable, return all reachable unwatched movies. ### Follow-up (production thinking) What potential issues might arise when deploying this in production (e.g., caching, concurrency, hot keys, stale data), and how would you mitigate them?

Quick Answer: This question evaluates understanding of graph traversal and shortest-path distance concepts along with multi-criteria ranking (distance, rating, ID) and efficient exploration of large undirected graphs to avoid revisiting nodes.

Part 1: Recommend top-K movies from similarity graph

Recommend up to **`k`** movies for a user, given a similarity graph between movies and each movie's rating. ## Problem You are building a movie recommender. Movies form an **undirected similarity graph**: movie `i` is directly similar to every movie listed in `adj[i]`. Each movie also has a numeric rating. Starting from a movie `start` that the user liked, return the list of recommended movie IDs (most relevant first), choosing them by graph distance, then rating, then ID. ## Function signature ```python def solution(adj, rating, start, watched, k): ``` ## Inputs - **`adj`** — adjacency list. `adj[i]` is a list of movie IDs directly connected to movie `i`. There are `n = len(adj)` movies, with IDs `0 .. n - 1`. - **`rating`** — list of length `n`; `rating[i]` is the rating of movie `i`. - **`start`** — ID of the movie the user liked (the starting node). - **`watched`** — list of movie IDs the user has already watched. - **`k`** — the maximum number of recommendations to return. ## Output Return a **list of up to `k` movie IDs** in recommendation order. ## Eligibility A movie may be recommended only if **all** of the following hold: - it is **reachable** from `start` in the graph, **and** - it is **not** in `watched`, **and** - it is **not** the `start` movie itself. ## Ranking Order all eligible movies by these tie-breakers, in priority order: 1. **Smaller graph distance** from `start` comes first (distance = number of edges on the shortest path). 2. If distances tie, **higher rating** comes first. 3. If both distance and rating tie, **smaller movie ID** comes first. If fewer than `k` eligible movies are reachable, return **all** of them in this order. ## Examples **Example 1** ``` adj = [[1, 2], [0, 3], [0, 3, 4], [1, 2], [2]] rating = [5, 7, 6, 9, 8] start = 0 watched = [0, 2] k = 3 => [1, 3, 4] ``` Movie `2` is excluded (in `watched`) and `0` is the start. Movie `1` is at distance 1; movies `3` and `4` are at distance 2 (so `1` comes before them). Among the distance-2 movies, `3` (rating 9) ranks above `4` (rating 8). **Example 2** ``` adj = [[1, 2], [0, 3], [0, 4], [1], [2]] rating = [1, 8, 8, 7, 9] start = 0 watched = [0] k = 4 => [1, 2, 4, 3] ``` Movies `1` and `2` are at distance 1 with equal ratings (8 and 8), so the smaller ID `1` comes first. Movies `3` and `4` are at distance 2; `4` (rating 9) outranks `3` (rating 7). ## Notes and edge cases - If `k <= 0`, or if `start` is outside the range `[0, n - 1]`, return an empty list. - The graph is undirected, but the input may contain **repeated/duplicate edges**; make sure you do not revisit nodes (avoid infinite loops). - `watched` may or may not contain `start`; either way, `start` must never be recommended. ## Constraints - `1 <= len(adj) == len(rating) <= 100000` - `0 <= start < len(adj)` - `0 <= k <= len(adj)` - Each neighbor in `adj[i]` is a valid movie ID in the range `[0, len(adj) - 1]`

Constraints

  • 1 <= len(adj) == len(rating) <= 100000
  • 0 <= start < len(adj)
  • 0 <= k <= len(adj)
  • Each neighbor in adj[i] is a valid movie ID in the range [0, len(adj) - 1]
  • The graph is undirected, but the input may still contain repeated edges; do not revisit nodes infinitely
  • watched may or may not contain start, but start must never be recommended

Examples

Input: ([[1, 2], [0, 3], [0, 3, 4], [1, 2], [2]], [5, 7, 6, 9, 8], 0, [0, 2], 3)

Expected Output: [1, 3, 4]

Explanation: Movie 1 is distance 1 and unwatched. Movies 3 and 4 are distance 2. Among distance-2 movies, rating 9 beats rating 8.

Input: ([[1, 2], [0, 3], [0, 4], [1], [2]], [1, 8, 8, 7, 9], 0, [0], 4)

Expected Output: [1, 2, 4, 3]

Explanation: Movies 1 and 2 are both distance 1 with equal rating, so smaller ID 1 comes first. At distance 2, movie 4 has higher rating than movie 3.

Hints

  1. Use BFS because every edge has the same cost, so BFS gives the shortest hop distance.
  2. Process one BFS layer at a time. All movies found in the same layer have the same distance, so you only need to sort that layer by rating and movie ID.

Part 2: Classify stale and hot cache keys for movie recommendations

Classify every starting movie in a recommendation cache as **stale**, **hot**, both, or neither, and return one action per movie. ## Background A production recommendation service caches results keyed by a **starting movie ID**. Each cached entry was computed from a list of **dependency movie IDs** that influenced that recommendation. If any of those dependency movies has changed, the cached result is **stale** and must be recomputed. A starting movie is **hot** if it appears frequently in recent requests, and hot keys should be protected with mitigations such as request coalescing or per-key throttling. ## Task Implement: ```python def solution(requests, cache_entries, updated_movies, hot_threshold): ``` ### Parameters - **`requests`** — a list of integer movie IDs representing recent requests. A movie may appear multiple times. - **`cache_entries`** — a dict mapping each cached **starting movie ID** to a list of its **dependency movie IDs**. A dependency list may be empty. - **`updated_movies`** — a list of integer movie IDs that have changed. - **`hot_threshold`** — an integer. ### Which movies to classify Produce an action for **every** starting movie that appears in **either**: - the `requests` list, **or** - the keys of `cache_entries`. (A movie may appear in only one of these. A movie present in `requests` but absent from `cache_entries` simply has no dependencies.) ### Classification rules For each such movie, determine two independent flags: - **stale** — `True` if **at least one** of the movie's dependency IDs (from its `cache_entries` list) is in `updated_movies`. A movie with **no cache entry** or an **empty** dependency list is **not** stale. - **hot** — `True` if the movie's number of occurrences in `requests` is **greater than or equal to** `hot_threshold`. A movie not present in `requests` has a count of `0`. Map the two flags to an action: | stale | hot | action | |-------|-----|--------| | yes | yes | `recompute_and_protect` | | yes | no | `recompute` | | no | yes | `protect_hot_key` | | no | no | `ok` | ## Output Return a list of strings, one per classified movie, each formatted as: ``` "movie_id:action" ``` The list must be **sorted by `movie_id` in ascending order**. ## Notes - Movie IDs are non-negative integers. - If no movie qualifies (both `requests` and `cache_entries` are empty), return an empty list. ### Example ``` requests = [1, 2, 1, 3, 1, 2] cache_entries = {1: [1, 4, 5], 2: [2, 7], 4: [8]} updated_movies = [5, 7] hot_threshold = 3 -> ["1:recompute_and_protect", "2:recompute", "3:ok", "4:ok"] ``` - Movie `1`: appears 3 times (≥ 3 → hot); dependency `5` is in `updated_movies` (stale) → `recompute_and_protect`. - Movie `2`: appears 2 times (< 3 → not hot); dependency `7` is updated (stale) → `recompute`. - Movie `3`: appears once (not hot) and has no cache entry (not stale) → `ok`. - Movie `4`: never requested (not hot); dependency `8` is not updated (not stale) → `ok`.

Constraints

  • 0 <= len(requests) <= 200000
  • 0 <= sum(len(v) for v in cache_entries.values()) <= 200000
  • 1 <= hot_threshold <= 1000000000
  • Movie IDs are non-negative integers
  • cache_entries may contain keys with empty dependency lists

Examples

Input: ([1, 2, 1, 3, 1, 2], {1: [1, 4, 5], 2: [2, 7], 4: [8]}, [5, 7], 3)

Expected Output: ["1:recompute_and_protect", "2:recompute", "3:ok", "4:ok"]

Explanation: Key 1 is requested 3 times and depends on updated movie 5, so it is both hot and stale. Key 2 is stale only. Key 3 is requested but not hot. Key 4 is cached but unaffected.

Input: ([5, 5, 5, 6], {2: [1], 5: [9], 6: [10]}, [11], 2)

Expected Output: ["2:ok", "5:protect_hot_key", "6:ok"]

Explanation: No cache entry is stale because updated movie 11 is not in any dependency list. Key 5 is hot because it appears 3 times, which is at least 2.

Hints

  1. First count how many times each movie ID appears in requests to find hot keys.
  2. Convert updated_movies to a set so you can test whether a cache entry is stale by scanning its dependency list once.

Loading coding console...