Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1809. Ad-Free Sessions - Leetcode Solution

Problem Description

The Ad-Free Sessions problem requires you to analyze user listening sessions and determine which sessions were ad-free. You are given a list of events, each representing a user's activity during a session. Each event contains information such as the user_id, session_id, and event_type (which can be "song", "ad", or other types).

Your task is to find all sessions (identified by session_id) that did not contain any ad events. In other words, for each session, if there is no event of type "ad", that session is considered ad-free and should be included in your output.

Key constraints:

  • Each session is uniquely identified by session_id.
  • Sessions may have multiple events.
  • Do not include sessions that had at least one ad event.
  • The output should be a list of ad-free session_ids.

Thought Process

The main challenge is to efficiently determine which sessions never had an "ad" event. A naive approach would be to check every event in every session, collecting all session IDs and then filtering out those which had an "ad". However, this could be inefficient if there are many users and sessions.

A better approach is to process the list of events in a single pass, keeping track of sessions that have had ads. After collecting all sessions and the ones with ads, we can subtract the set of sessions with ads from all session IDs to get the ad-free sessions.

This approach leverages set operations for efficiency, and avoids redundant checks. We want to avoid re-examining the same session multiple times, and we should not count a session as ad-free if it ever had an ad, even if it also had other event types.

Solution Approach

Let's break down the steps to solve the problem efficiently:

  1. Initialize two sets:
    • One set (all_sessions) to store all unique session_ids found in the events.
    • Another set (sessions_with_ads) to store session_ids that have at least one "ad" event.
  2. Iterate through each event:
    • Add the session_id to all_sessions.
    • If the event_type is "ad", also add the session_id to sessions_with_ads.
  3. Find ad-free sessions:
    • Subtract sessions_with_ads from all_sessions. The result is the set of sessions with no ad events.
  4. Return or output the result:
    • Return the sorted list of ad-free session_ids, as required.

Why use sets? Sets allow for fast membership checking and set subtraction, making it efficient to identify ad-free sessions.

Example Walkthrough

Consider the following sample input:

  • Event 1: user_id=1, session_id=100, event_type="song"
  • Event 2: user_id=1, session_id=100, event_type="ad"
  • Event 3: user_id=2, session_id=101, event_type="song"
  • Event 4: user_id=2, session_id=101, event_type="song"
  • Event 5: user_id=3, session_id=102, event_type="ad"
  • Event 6: user_id=3, session_id=103, event_type="song"

Step-by-step:

  • Add all session IDs: {100, 101, 102, 103}
  • Sessions with ads: 100 (Event 2), 102 (Event 5) → {100, 102}
  • Ad-free sessions: {101, 103} (since 100 and 102 had ads)

The output is [101, 103].

Time and Space Complexity

Brute-force approach:

  • Time: O(N * S), where N is the number of events and S is the number of sessions (if you check each session separately).
  • Space: O(S) for storing session IDs.
Optimized set-based approach:
  • Time: O(N), since each event is processed exactly once.
  • Space: O(S), where S is the number of unique sessions (for the sets).

The set-based solution is optimal for both time and space, given the constraints.

Code Implementation

def adFreeSessions(events):
    all_sessions = set()
    sessions_with_ads = set()
    for event in events:
        session_id = event['session_id']
        all_sessions.add(session_id)
        if event['event_type'] == 'ad':
            sessions_with_ads.add(session_id)
    ad_free = sorted(all_sessions - sessions_with_ads)
    return ad_free

# Example usage:
# events = [
#     {'user_id': 1, 'session_id': 100, 'event_type': 'song'},
#     {'user_id': 1, 'session_id': 100, 'event_type': 'ad'},
#     {'user_id': 2, 'session_id': 101, 'event_type': 'song'},
#     {'user_id': 2, 'session_id': 101, 'event_type': 'song'},
#     {'user_id': 3, 'session_id': 102, 'event_type': 'ad'},
#     {'user_id': 3, 'session_id': 103, 'event_type': 'song'},
# ]
# print(adFreeSessions(events))  # Output: [101, 103]
    
#include <vector>
#include <set>
#include <map>
#include <string>
#include <algorithm>
using namespace std;

struct Event {
    int user_id;
    int session_id;
    string event_type;
};

vector<int> adFreeSessions(const vector<Event>& events) {
    set<int> all_sessions;
    set<int> sessions_with_ads;
    for (const auto& event : events) {
        all_sessions.insert(event.session_id);
        if (event.event_type == "ad") {
            sessions_with_ads.insert(event.session_id);
        }
    }
    vector<int> ad_free;
    for (int session_id : all_sessions) {
        if (sessions_with_ads.find(session_id) == sessions_with_ads.end()) {
            ad_free.push_back(session_id);
        }
    }
    sort(ad_free.begin(), ad_free.end());
    return ad_free;
}

// Example usage:
// vector<Event> events = {
//     {1, 100, "song"}, {1, 100, "ad"}, {2, 101, "song"},
//     {2, 101, "song"}, {3, 102, "ad"}, {3, 103, "song"}
// };
// auto result = adFreeSessions(events); // result: {101, 103}
    
import java.util.*;

class Event {
    int user_id;
    int session_id;
    String event_type;
    public Event(int user_id, int session_id, String event_type) {
        this.user_id = user_id;
        this.session_id = session_id;
        this.event_type = event_type;
    }
}

public class Solution {
    public List<Integer> adFreeSessions(List<Event> events) {
        Set<Integer> allSessions = new HashSet<>();
        Set<Integer> sessionsWithAds = new HashSet<>();
        for (Event event : events) {
            allSessions.add(event.session_id);
            if ("ad".equals(event.event_type)) {
                sessionsWithAds.add(event.session_id);
            }
        }
        List<Integer> adFree = new ArrayList<>();
        for (int sessionId : allSessions) {
            if (!sessionsWithAds.contains(sessionId)) {
                adFree.add(sessionId);
            }
        }
        Collections.sort(adFree);
        return adFree;
    }
}

// Example usage:
// List<Event> events = Arrays.asList(
//     new Event(1, 100, "song"),
//     new Event(1, 100, "ad"),
//     new Event(2, 101, "song"),
//     new Event(2, 101, "song"),
//     new Event(3, 102, "ad"),
//     new Event(3, 103, "song")
// );
// List<Integer> result = new Solution().adFreeSessions(events); // [101, 103]
    
function adFreeSessions(events) {
    const allSessions = new Set();
    const sessionsWithAds = new Set();
    for (const event of events) {
        allSessions.add(event.session_id);
        if (event.event_type === 'ad') {
            sessionsWithAds.add(event.session_id);
        }
    }
    const adFree = [];
    for (const sessionId of allSessions) {
        if (!sessionsWithAds.has(sessionId)) {
            adFree.push(sessionId);
        }
    }
    adFree.sort((a, b) => a - b);
    return adFree;
}

// Example usage:
// const events = [
//     {user_id: 1, session_id: 100, event_type: "song"},
//     {user_id: 1, session_id: 100, event_type: "ad"},
//     {user_id: 2, session_id: 101, event_type: "song"},
//     {user_id: 2, session_id: 101, event_type: "song"},
//     {user_id: 3, session_id: 102, event_type: "ad"},
//     {user_id: 3, session_id: 103, event_type: "song"}
// ];
// console.log(adFreeSessions(events)); // [101, 103]
    

Summary

The Ad-Free Sessions problem asks us to identify sessions without any ad events from a list of user activity events. By leveraging sets, we efficiently track all sessions and those with ads, then use set subtraction to find the ad-free sessions. This approach is both simple and optimal, requiring only a single pass through the data and minimal extra memory. The key insight is to utilize set operations to avoid redundant checks and ensure accuracy, making the solution both elegant and efficient.