Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1311. Get Watched Videos by Your Friends - Leetcode Solution

Problem Description

You are given a list of users, where each user has a list of friends and a list of videos they have watched. The information is represented as:

  • watchedVideos: A list where watchedVideos[i] is a list of strings representing videos watched by the i-th user.
  • friends: A list where friends[i] is a list of user IDs (integers) representing the friends of the i-th user.
  • id: The ID of the starting user.
  • level: The friendship level to consider.

Your task is: Find all the videos watched by friends at exactly level friendship distance from the given user id. Return a sorted list of video names ordered first by the frequency of viewing (ascending), then lexicographically.

Key constraints:

  • Each friendship is bidirectional.
  • Do not count videos watched by users at other levels.
  • Do not count the starting user's own videos.
  • Each user and video name is unique.

Thought Process

The core of the problem is to find all friends at a specific distance (level) from a given user and aggregate the videos they have watched. The naive approach would be to look through all users and check their distance, but this is inefficient and error-prone. Instead, we can model the friendship network as a graph and use Breadth-First Search (BFS) to find all friends at the required level.

Once we have the correct set of friends, we gather their watched videos and count their frequencies. Sorting the results by frequency and then by name ensures the answer is in the right order.

Optimization comes from:

  • Using BFS to efficiently find friends at the exact level.
  • Using a hash map (dictionary) to count video frequencies quickly.
  • Sorting with a custom key to satisfy the output constraints.

Solution Approach

Let's break down the solution into clear steps:

  1. BFS Traversal:
    • Start from the given user id.
    • Use a queue to perform BFS, tracking the current level.
    • Keep a set of visited users to avoid cycles and revisiting.
    • Stop expanding when you reach the desired level.
  2. Collect Videos:
    • Once at the correct level, collect the user IDs in the queue.
    • For each of these users, gather their watched videos.
    • Use a hash map to count how many times each video appears.
  3. Sort and Return:
    • Sort the videos first by frequency (ascending), then lexicographically.
    • Return the sorted list of video names.

We use BFS because it naturally explores nodes in order of their distance from the starting node, making it ideal for "level"-based problems in graphs.

Example Walkthrough

Consider the following example:

  • watchedVideos = [["A","B"],["C"],["B","C"],["D"]]
  • friends = [[1,2],[0,3],[0,3],[1,2]]
  • id = 0
  • level = 1

Steps:

  1. BFS:
    • Start at user 0. Level 0: [0]
    • Level 1: Friends of 0 are [1,2]
  2. Collect Videos:
    • User 1 watched ["C"], user 2 watched ["B","C"]
    • Video counts: "B": 1, "C": 2
  3. Sort:
    • Sort by frequency: "B" (1), "C" (2)
    • Final output: ["B","C"]

At each BFS level, we only expand friends who haven't been visited yet, ensuring we don't count the same user twice.

Time and Space Complexity

Brute-force Approach:

  • Would require checking all users, calculating their distance from the starting user, then aggregating videos. This could be O(N^2) for N users.
Optimized (BFS) Approach:
  • Time Complexity: O(N + E + V log V), where N = number of users, E = number of friendships, V = number of unique videos. BFS takes O(N + E), counting videos is O(V), and sorting is O(V log V).
  • Space Complexity: O(N + V), for visited set, queue, and video frequency map.

Using BFS ensures we only visit each user once, and the hash map makes counting efficient.

Summary

This problem combines graph traversal with frequency counting and custom sorting. By modeling friendships as a graph and using BFS, we efficiently find all friends at the required level. Hash maps allow quick aggregation of video counts, and a simple sort gives us the required output order. The approach is both efficient and elegant, leveraging standard graph algorithms and data structures to solve a real-world scenario.

Code Implementation

from collections import deque, Counter

class Solution:
    def watchedVideosByFriends(self, watchedVideos, friends, id, level):
        n = len(friends)
        visited = set()
        queue = deque()
        queue.append(id)
        visited.add(id)
        curr_level = 0

        while queue and curr_level < level:
            for _ in range(len(queue)):
                user = queue.popleft()
                for f in friends[user]:
                    if f not in visited:
                        visited.add(f)
                        queue.append(f)
            curr_level += 1

        # queue now contains users at the exact 'level'
        freq = Counter()
        for user in queue:
            freq.update(watchedVideos[user])

        result = sorted(freq.items(), key=lambda x: (x[1], x[0]))
        return [video for video, _ in result]
      
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
using namespace std;

class Solution {
public:
    vector<string> watchedVideosByFriends(vector<vector<string>>& watchedVideos, vector<vector<int>>& friends, int id, int level) {
        int n = friends.size();
        vector<bool> visited(n, false);
        queue<int> q;
        q.push(id);
        visited[id] = true;
        int curr_level = 0;

        while (!q.empty() && curr_level < level) {
            int sz = q.size();
            for (int i = 0; i < sz; ++i) {
                int user = q.front(); q.pop();
                for (int f : friends[user]) {
                    if (!visited[f]) {
                        visited[f] = true;
                        q.push(f);
                    }
                }
            }
            curr_level++;
        }

        unordered_map<string, int> freq;
        while (!q.empty()) {
            int user = q.front(); q.pop();
            for (const string& video : watchedVideos[user]) {
                freq[video]++;
            }
        }

        vector<pair<string, int>> vids(freq.begin(), freq.end());
        sort(vids.begin(), vids.end(), [](const pair<string, int>& a, const pair<string, int>& b) {
            if (a.second == b.second) return a.first < b.first;
            return a.second < b.second;
        });

        vector<string> result;
        for (const auto& p : vids) result.push_back(p.first);
        return result;
    }
};
      
import java.util.*;

class Solution {
    public List<String> watchedVideosByFriends(List<List<String>> watchedVideos, List<List<Integer>> friends, int id, int level) {
        int n = friends.size();
        Queue<Integer> queue = new LinkedList<>();
        boolean[] visited = new boolean[n];
        queue.offer(id);
        visited[id] = true;
        int currLevel = 0;

        while (!queue.isEmpty() && currLevel < level) {
            int sz = queue.size();
            for (int i = 0; i < sz; i++) {
                int user = queue.poll();
                for (int f : friends.get(user)) {
                    if (!visited[f]) {
                        visited[f] = true;
                        queue.offer(f);
                    }
                }
            }
            currLevel++;
        }

        Map<String, Integer> freq = new HashMap<>();
        for (int user : queue) {
            for (String video : watchedVideos.get(user)) {
                freq.put(video, freq.getOrDefault(video, 0) + 1);
            }
        }

        List<String> result = new ArrayList<>(freq.keySet());
        result.sort((a, b) -> {
            int cmp = freq.get(a) - freq.get(b);
            if (cmp == 0) return a.compareTo(b);
            return cmp;
        });

        return result;
    }
}
      
/**
 * @param {string[][]} watchedVideos
 * @param {number[][]} friends
 * @param {number} id
 * @param {number} level
 * @return {string[]}
 */
var watchedVideosByFriends = function(watchedVideos, friends, id, level) {
    let n = friends.length;
    let visited = new Array(n).fill(false);
    let queue = [id];
    visited[id] = true;
    let currLevel = 0;

    while (queue.length > 0 && currLevel < level) {
        let sz = queue.length;
        for (let i = 0; i < sz; i++) {
            let user = queue.shift();
            for (let f of friends[user]) {
                if (!visited[f]) {
                    visited[f] = true;
                    queue.push(f);
                }
            }
        }
        currLevel++;
    }

    let freq = {};
    for (let user of queue) {
        for (let video of watchedVideos[user]) {
            freq[video] = (freq[video] || 0) + 1;
        }
    }

    let vids = Object.keys(freq);
    vids.sort((a, b) => {
        if (freq[a] === freq[b]) return a.localeCompare(b);
        return freq[a] - freq[b];
    });

    return vids;
};