Get Watched Videos by Your Friends: BFS + Frequency Counting
You are given a list watchedVideos where watchedVideos[i] contains the videos person i has watched, a friends adjacency list where friends[i] lists person i's direct friends, a starting person id, and an integer level. Return all videos watched by people who are exactly level friendships away from id, sorted by frequency (ascending), with ties broken alphabetically.
This is LeetCode 1311: Get Watched Videos by Your Friends, and it combines three fundamental patterns into one clean problem: BFS for exact-distance neighbors, hash map frequency counting, and custom sorting.
Why this problem matters
Interview problems that blend multiple techniques are the ones that trip people up. You might know BFS. You might know frequency counting. You might know custom sorting. But doing all three in sequence, under time pressure, without mixing anything up? That requires practice.
This problem is also a realistic scenario. Think of a social network where you want to recommend content based on what friends-of-friends are watching. The graph traversal finds the right audience, the frequency counting ranks the content, and the sort produces the final recommendation list. It is the kind of pipeline you see in production systems.
The key insight
Break the problem into three clean phases:
- BFS to exact level k: Use level-by-level BFS (not DFS) to find all people at exactly distance
levelfromid. Track visited people to avoid revisiting anyone closer. - Collect and count: Gather all videos watched by those level-k friends into a frequency map using a
Counter. - Custom sort: Sort the videos by
(frequency, video_name)so lower-frequency videos come first and ties are broken alphabetically.
The key is that BFS naturally processes nodes level by level. After exactly level iterations of expanding the queue, everyone in the current queue is at the right distance.
The solution
from collections import deque, Counter
def watched_videos_by_friends(watched_videos, friends, id, level):
visited = {id}
queue = deque([id])
for _ in range(level):
next_queue = deque()
for person in queue:
for friend in friends[person]:
if friend not in visited:
visited.add(friend)
next_queue.append(friend)
queue = next_queue
freq = Counter()
for person in queue:
for video in watched_videos[person]:
freq[video] += 1
return sorted(freq.keys(), key=lambda v: (freq[v], v))
The function has three distinct sections. First, BFS runs for exactly level iterations. Each iteration drains the current queue and builds a new one with unvisited neighbors. After the loop, queue holds exactly the people at distance level.
Second, we iterate over those people and count every video they have watched using a Counter. This gives us a frequency map from video name to count.
Third, we sort the video names using a tuple key (freq[v], v). Python sorts tuples lexicographically, so this sorts by frequency first (ascending), then by name alphabetically for ties.
BFS processes nodes level by level naturally when you drain the entire queue in each iteration and build a fresh queue for the next level. This is the same technique used in level-order tree traversal and multi-source BFS problems.
Visual walkthrough
Step 1: BFS from the starting person
Start BFS from person 0 (id=0). Initialize queue = [0] and visited = {0}. We need to find all friends at exactly level k=2, so we process the graph level by level.
Level-by-level BFS is essential here. We only care about people at exactly distance k, not closer.
Step 2: Process BFS levels to find level-k friends
Expand the queue level by level. At each level, add unvisited neighbors to the next queue. After k iterations, the current queue holds exactly the level-k friends.
| Level | Queue (people at this level) | Visited after | Notes |
|---|---|---|---|
| 0 | 0 | {0} | Starting person |
| 1 | 1, 2 | {0, 1, 2} | Direct friends of person 0 |
| 2 | 3 | {0, 1, 2, 3} | Friends at distance 2 (target level) |
After 2 BFS levels, person 3 is the only friend at exactly distance 2.
Step 3: Collect videos from level-k friends
Gather all videos watched by the people found at level 2. Person 3 watched ['D'].
| Person | Watched videos |
|---|---|
| 3 | D |
If there were multiple level-k friends, we would collect all their videos.
Step 4: Count video frequencies
Use a hash map (Counter) to count how many times each video appears across all level-k friends.
| Video | Frequency |
|---|---|
| D | 1 |
With more people, the same video might appear multiple times (e.g., if two friends both watched 'C').
Step 5: Sort by frequency, then alphabetically
Sort the videos first by frequency (ascending), then alphabetically for ties. With just {"D": 1}, the result is simply ["D"]. For a richer example, if frequencies were {"B": 2, "C": 2, "D": 1}, the sorted result would be ["D", "B", "C"] since D has the lowest frequency, and B comes before C alphabetically among the tied frequency-2 videos.
The custom sort key (frequency, name) is the final piece of the puzzle.
Notice how each phase feeds cleanly into the next. BFS produces the set of people. The people produce the video list. The video list produces the frequency map. The frequency map produces the sorted result. Each step is simple on its own.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| BFS + Frequency sort | O(n + v log v) | O(n + v) |
Here n is the total number of people (nodes plus edges traversed during BFS) and v is the number of distinct videos among the level-k friends.
Time: BFS visits each person at most once and checks each friendship edge at most once, giving O(n). Collecting videos is O(total videos watched by level-k friends). Sorting the distinct videos is O(v log v). The dominant term depends on the input, but the overall bound is O(n + v log v).
Space: The visited set and queue together hold up to O(n) people. The frequency map holds up to O(v) entries. Total: O(n + v).
The building blocks
1. BFS to exact level
visited = {id}
queue = deque([id])
for _ in range(level):
next_queue = deque()
for person in queue:
for friend in friends[person]:
if friend not in visited:
visited.add(friend)
next_queue.append(friend)
queue = next_queue
This is the standard "BFS by level" pattern. Instead of processing one node at a time, you process an entire level before moving on. The outer loop controls how many levels deep you go. After level iterations, the queue contains exactly the nodes at distance level from the start. The visited set prevents you from walking backward to closer nodes.
2. Frequency counting and custom sort
freq = Counter()
for person in queue:
for video in watched_videos[person]:
freq[video] += 1
result = sorted(freq.keys(), key=lambda v: (freq[v], v))
This is a classic frequency counting pattern. You loop through all the data, tally up occurrences in a hash map, then sort by the counts. The tuple key (freq[v], v) is the standard Python idiom for multi-criteria sorting. The first element is the primary sort key, and the second breaks ties. Since Python's sorted is stable and sorts tuples element by element, this gives you ascending frequency with alphabetical tiebreaking.
Edge cases
- Level 0: The only "friend" at distance 0 is the person themselves. Return their own videos sorted.
- No friends at level k: If BFS runs out of people before reaching level k, the queue will be empty. Return an empty list.
- All friends watch the same video: The frequency map has one entry. The sorted result is a single-element list.
- Multiple friends, all videos unique: Every video has frequency 1. The result is just the videos sorted alphabetically.
- Large graphs with cycles: The visited set ensures each person is counted at most once, even if there are multiple paths to them.
From understanding to recall
You can see how BFS, frequency counting, and custom sorting combine here. Each piece is a pattern you have likely seen before. The challenge is executing all three in sequence without bugs. Did you remember to use a fresh queue each BFS level? Did you sort by (frequency, name) and not just by name? Did you initialize the visited set with the starting person?
Spaced repetition drills each of these building blocks independently. You practice BFS-by-level until the queue-swap pattern is automatic. You practice frequency counting until Counter feels like second nature. Then when a problem asks for all three, you just chain them together without hesitation.
Related posts
- Course Schedule - Graph BFS/DFS for dependency resolution
- Clone Graph - BFS graph traversal pattern
- Top K Frequent Elements - Frequency counting and sorting
CodeBricks breaks Get Watched Videos by Your Friends into its BFS-level-traversal and frequency-sort building blocks, then drills them independently with spaced repetition. You type each piece from scratch until the pattern is automatic. When a problem combines graph traversal with frequency counting, you do not re-derive the approach. You just write it.