Solve friend recommendations and time-versioned maps
Company: Lead
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This question evaluates the candidate's ability to design and implement specialized data structures for graph-based friend recommendation logic and time-versioned key-value storage, testing competencies in algorithmic design, data modeling, and complexity analysis.
Constraints
- 0 <= len(like_records) <= 10^5
- 0 <= len(operations) <= 10^5
- User IDs and keys are non-empty strings
- 1 <= like_count <= 10^9
- Timestamps are integers in the range [-10^9, 10^9]
- Stored values are not None, because None is used as the missing-value sentinel
- For deterministic recommendations, ties are broken by lexicographically smallest user ID
Examples
Input: ([('alice', 'bob', 5), ('bob', 'carol', 4), ('bob', 'dave', 4), ('alice', 'erin', 2)], [('getLikeCount', 'alice', 'bob'), ('getLikeCount', 'alice', 'carol'), ('recommendFriend', 'alice'), ('put', 'x', 'one', 10), ('put', 'y', 'why', 5), ('get', 'x', 9), ('get', 'x', 10), ('snapshot', 10)])
Expected Output: [5, 0, 'carol', None, 'one', {'x': 'one', 'y': 'why'}]
Explanation: Alice likes Bob most. Bob's top liked users are Carol and Dave with equal weight, so the deterministic recommendation is Carol. The versioned map returns None before key x exists, then returns the visible values at timestamp 10.
Input: ([('u', 'a', 10), ('u', 'b', 10), ('a', 'u', 7), ('a', 'd', 5), ('b', 'c', 6), ('b', 'e', 6)], [('recommendFriend', 'u'), ('getLikeCount', 'u', 'a'), ('getLikeCount', 'a', 'u'), ('put', 'k', 'old', 1), ('put', 'k', 'override', 1), ('get', 'k', 1), ('snapshot', 1)])
Expected Output: ['c', 10, 7, 'override', {'k': 'override'}]
Explanation: User u's top favorites are a and b. Candidate u from a is invalid because it is the original user, so the algorithm continues to the next candidate like count. Candidates c and e are tied through b, and c is lexicographically smaller. For the map, the second put at timestamp 1 overrides the first.
Input: ([('ann', 'bob', 3), ('bob', 'ann', 2), ('bob', 'cat', 5), ('ann', 'cat', 1), ('cat', 'ann', 1)], [('recommendFriend', 'ann'), ('put', 'a', 'first', -5), ('put', 'b', 'bee', 0), ('get', 'a', -6), ('get', 'a', -5), ('snapshot', -1), ('snapshot', 100)])
Expected Output: [None, None, 'first', {'a': 'first'}, {'a': 'first', 'b': 'bee'}]
Explanation: Ann's best intermediate is Bob. Bob likes Cat most, but Cat is already Ann's friend because Ann and Cat liked each other. Bob's other candidate is Ann, which is invalid, so there is no recommendation. The timestamp tests include negative timestamps and snapshots before and after key b exists.
Input: ([], [('recommendFriend', 'nobody'), ('getLikeCount', 'x', 'y'), ('get', 'missing', 100), ('snapshot', 100), ('put', 'z', 'zed', 50), ('get', 'z', 49), ('get', 'z', 50)])
Expected Output: [None, 0, None, {}, None, 'zed']
Explanation: With an empty like graph, recommendations are impossible and missing like counts are zero. The map starts empty, so missing reads and snapshots return None and an empty dictionary respectively.
Hints
- For the like graph, store outgoing edges in nested hash maps and group each user's outgoing neighbors by like count.
- For the time-versioned map, keep a sorted list of timestamps per key and use binary search to find the greatest timestamp not exceeding the query timestamp.