Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1919. Leetcodify Similar Friends - Leetcode Solution

Problem Description

The "Leetcodify Similar Friends" problem provides you with a list of users, each having a list of solved LeetCode problems. Your task is to find all pairs of users who are "Leetcodify similar friends." Two users are considered Leetcodify similar friends if:

  • They have solved at least k common problems (where k is a given integer).
  • The pair should be listed only once (i.e., (A, B) is the same as (B, A)).
  • No user should be paired with themselves.
The input is typically provided as a list of users, each with their own list of solved problem IDs, and an integer k.

The output should be a list of all unique user pairs who meet the similarity condition.

Thought Process

To solve this problem, we first need to compare every pair of users to determine if they share at least k solved problems. The naive approach would be to check every possible pair and count the intersection of their solved problem lists.

However, since the number of users and problems could be large, this brute-force method may be inefficient. We need to think about optimizing the way we compare users. By representing each user's solved problems as a set, we can quickly compute intersections. Additionally, to avoid duplicate pairs, we can ensure we only consider each unordered pair once (e.g., by always pairing user1 with user2 where user1 < user2).

The key is to balance between correctness (finding all valid pairs) and efficiency (not recalculating intersections or comparing users unnecessarily).

Solution Approach

Here's how we can approach the problem step by step:

  1. Store each user's solved problems as a set:
    • This allows for efficient intersection operations (O(min(n, m)) time for two sets).
  2. Iterate through all unique user pairs:
    • For n users, there are n * (n - 1) / 2 unique pairs.
    • For each pair, compute the intersection of their problem sets.
  3. Check if the intersection size is at least k:
    • If so, add the pair to the result list.
  4. Return the list of valid pairs:
    • Ensure each pair is listed only once, and no user is paired with themselves.

This approach is efficient and straightforward, leveraging set operations to speed up intersection checks.

Example Walkthrough

Suppose we have the following input:

  • users = [ ["alice", [1,2,3,4]], ["bob", [2,3,5]], ["carol", [1,2,3,4,5,6]] ]
  • k = 3
Step-by-step:
  1. alice & bob:
    • Common problems: 2, 3
    • Intersection size = 2 < 3 → Not added
  2. alice & carol:
    • Common problems: 1, 2, 3, 4
    • Intersection size = 4 ≥ 3 → Add ("alice", "carol")
  3. bob & carol:
    • Common problems: 2, 3, 5
    • Intersection size = 3 ≥ 3 → Add ("bob", "carol")
Final output: [("alice", "carol"), ("bob", "carol")]

Time and Space Complexity

Brute-force approach:

  • For each pair of users, compare their lists directly. If each list has up to m problems, and there are n users, time is O(n2 * m).
Optimized approach:
  • By using sets, intersection is O(min(m1, m2)) for each pair.
  • Still O(n2) pairs, but set intersection is faster than list comparison.
  • Space: O(n * m) for storing all sets.
Summary:
  • Time: O(n2 * k) in practice, since we can short-circuit intersection if we reach k early.
  • Space: O(n * m) for the sets.

Summary

The key insight is to use set intersections for efficient comparison. By iterating over all unique user pairs and checking whether their solved problems overlap by at least k, we can quickly find all "Leetcodify similar friends." The solution is elegant because it leverages built-in data structures for speed, avoids redundant comparisons, and ensures output uniqueness.

Code Implementation

def leetcodify_similar_friends(users, k):
    # users: List of [username, [problem ids]]
    name_to_set = {name: set(problems) for name, problems in users}
    names = [name for name, _ in users]
    result = []
    n = len(names)
    for i in range(n):
        for j in range(i + 1, n):
            name1, name2 = names[i], names[j]
            if len(name_to_set[name1] & name_to_set[name2]) >= k:
                result.append((name1, name2))
    return result
      
#include <vector>
#include <string>
#include <unordered_set>
using namespace std;

vector<pair<string, string>> leetcodifySimilarFriends(
    vector<pair<string, vector<int>>>& users, int k) {
    int n = users.size();
    vector<unordered_set<int>> sets;
    vector<string> names;
    for (auto& user : users) {
        names.push_back(user.first);
        sets.push_back(unordered_set<int>(user.second.begin(), user.second.end()));
    }
    vector<pair<string, string>> result;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            int common = 0;
            for (int p : sets[i]) {
                if (sets[j].count(p)) {
                    ++common;
                    if (common >= k) break;
                }
            }
            if (common >= k) {
                result.push_back({names[i], names[j]});
            }
        }
    }
    return result;
}
      
import java.util.*;

public class LeetcodifySimilarFriends {
    public static List<List<String>> leetcodifySimilarFriends(
        List<Pair<String, List<Integer>>> users, int k) {
        int n = users.size();
        List<String> names = new ArrayList<>();
        List<Set<Integer>> sets = new ArrayList<>();
        for (Pair<String, List<Integer>> user : users) {
            names.add(user.getKey());
            sets.add(new HashSet<>(user.getValue()));
        }
        List<List<String>> result = new ArrayList<>();
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                int common = 0;
                for (int p : sets.get(i)) {
                    if (sets.get(j).contains(p)) {
                        ++common;
                        if (common >= k) break;
                    }
                }
                if (common >= k) {
                    result.add(Arrays.asList(names.get(i), names.get(j)));
                }
            }
        }
        return result;
    }
}
// Helper Pair class for demonstration
class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) { this.key = key; this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}
      
function leetcodifySimilarFriends(users, k) {
    // users: Array of [name, [problem ids]]
    const nameToSet = new Map();
    const names = [];
    for (const [name, problems] of users) {
        nameToSet.set(name, new Set(problems));
        names.push(name);
    }
    const result = [];
    for (let i = 0; i < names.length; ++i) {
        for (let j = i + 1; j < names.length; ++j) {
            const set1 = nameToSet.get(names[i]);
            const set2 = nameToSet.get(names[j]);
            let common = 0;
            for (const p of set1) {
                if (set2.has(p)) {
                    common += 1;
                    if (common >= k) break;
                }
            }
            if (common >= k) {
                result.push([names[i], names[j]]);
            }
        }
    }
    return result;
}