Facebook Pixel

3433. Count Mentions Per User

Problem Description

You have a system with a certain number of users that can be online or offline. You need to track how many times each user gets mentioned across various message events.

The problem gives you:

  • numberOfUsers: The total number of users (indexed from 0 to numberOfUsers-1)
  • events: An array where each event has 3 elements describing either a message or an offline event

There are two types of events:

1. Message Event: ["MESSAGE", "timestamp", "mentions_string"]

  • Occurs at a specific timestamp
  • The mentions_string can contain:
    • id<number>: Mentions a specific user (e.g., "id0", "id5"). Multiple ids can be separated by spaces and duplicates count as separate mentions
    • ALL: Mentions every single user in the system
    • HERE: Mentions only users who are currently online

2. Offline Event: ["OFFLINE", "timestamp", "user_id"]

  • Indicates that a user goes offline at the given timestamp
  • The user stays offline for exactly 60 time units
  • The user automatically comes back online at timestamp + 60

Important rules:

  • All users start as online
  • If events happen at the same timestamp, offline/online status changes are processed BEFORE message events
  • A user can be mentioned multiple times in a single message, and each mention counts separately
  • Offline users can still be mentioned with their specific id

Your task is to return an array where the value at index i represents the total number of times user i was mentioned across all message events.

For example, if user 0 is mentioned 3 times across different messages (including being part of an "ALL" mention), the result array at index 0 should be 3.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to process events in chronological order while carefully managing user online/offline states. Since status changes must be processed before messages at the same timestamp, we need a way to ensure proper ordering.

First, let's think about sorting. We sort events by timestamp, but when timestamps are equal, we need offline events to come first. This is achieved by comparing the third character of the event type string (e[0][2]) - "OFFLINE" has 'F' at index 2 while "MESSAGE" has 'S' at index 2, and 'F' comes before 'S' alphabetically.

For tracking online status, we can use an array online_t where each element represents when that user will next be online. If online_t[i] <= current_time, user i is online. When a user goes offline at time t, we set online_t[i] = t + 60.

The "ALL" mentions present an interesting optimization opportunity. Instead of immediately adding 1 to every user's count when we see "ALL", we can use a lazy evaluation approach. We accumulate these global mentions in a lazy counter and apply them all at once at the end. This is more efficient than updating every user's count multiple times.

For "HERE" mentions, we need to check each user's online status at the current timestamp and increment their count if they're online.

For specific user mentions (like "id0 id2 id0"), we parse the string, extract the user IDs, and increment their counts directly. Note that duplicates count separately, so if "id0" appears twice, user 0 gets 2 mentions.

The beauty of this approach is that by properly ordering events and maintaining just the next online time for each user, we can efficiently simulate the entire system without complex state management. The lazy evaluation for "ALL" mentions further optimizes performance by deferring work until necessary.

Learn more about Math and Sorting patterns.

Solution Approach

Let's walk through the implementation step by step:

1. Sorting the Events

events.sort(key=lambda e: (int(e[1]), e[0][2]))

We sort events primarily by timestamp (int(e[1])). For events with the same timestamp, we use the third character of the event type (e[0][2]) as a tiebreaker. This ensures "OFFLINE" events (with 'F' at index 2) come before "MESSAGE" events (with 'S' at index 2).

2. Initialize Data Structures

ans = [0] * numberOfUsers      # Stores mention count for each user
online_t = [0] * numberOfUsers  # Stores next online time for each user
lazy = 0                        # Accumulates "ALL" mentions
  • ans: The result array tracking mentions for each user
  • online_t: Initially all zeros, meaning all users are online (since 0 <= any timestamp)
  • lazy: Counter for deferred "ALL" mentions

3. Process Each Event

for etype, ts, s in events:
    cur = int(ts)

We iterate through sorted events and convert the timestamp to an integer for comparison.

4. Handle Different Event Types

OFFLINE Event:

if etype[0] == "O":
    online_t[int(s)] = cur + 60

Set the user's next online time to current time + 60.

MESSAGE Event with "ALL":

elif s[0] == "A":
    lazy += 1

Instead of immediately incrementing all users' counts, we defer this operation by incrementing lazy.

MESSAGE Event with "HERE":

elif s[0] == "H":
    for i, t in enumerate(online_t):
        if t <= cur:
            ans[i] += 1

Check each user's online status. If online_t[i] <= cur, the user is online and gets mentioned.

MESSAGE Event with Specific IDs:

else:
    for a in s.split():
        ans[int(a[2:])] += 1

Split the mention string by spaces, extract user IDs (skipping "id" prefix with a[2:]), and increment their counts. Duplicates are counted separately.

5. Apply Lazy Updates

if lazy:
    for i in range(numberOfUsers):
        ans[i] += lazy

Finally, apply all accumulated "ALL" mentions to every user.

This approach efficiently handles the simulation with O(n log n) time complexity for sorting and O(n × m) for processing events (where n is the number of events and m is the number of users), while maintaining O(m) space complexity for the tracking arrays.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with 3 users and the following events:

Input:

  • numberOfUsers = 3 (users 0, 1, 2)
  • events = [["MESSAGE", "10", "HERE"], ["OFFLINE", "15", "1"], ["MESSAGE", "15", "id1 id2"], ["MESSAGE", "20", "ALL"]]

Step 1: Sort the events After sorting by timestamp (and OFFLINE before MESSAGE for same timestamps):

  1. ["MESSAGE", "10", "HERE"] - timestamp 10
  2. ["OFFLINE", "15", "1"] - timestamp 15 (comes before MESSAGE at 15)
  3. ["MESSAGE", "15", "id1 id2"] - timestamp 15
  4. ["MESSAGE", "20", "ALL"] - timestamp 20

Step 2: Initialize data structures

  • ans = [0, 0, 0] - mention counts for users 0, 1, 2
  • online_t = [0, 0, 0] - all users online initially (next online time is 0)
  • lazy = 0 - deferred "ALL" mentions

Step 3: Process each event

Event 1: ["MESSAGE", "10", "HERE"] at time 10

  • Check who's online: All users have online_t[i] = 0 <= 10, so all are online
  • Increment each online user's count: ans = [1, 1, 1]

Event 2: ["OFFLINE", "15", "1"] at time 15

  • User 1 goes offline: online_t[1] = 15 + 60 = 75
  • Status now: online_t = [0, 75, 0]

Event 3: ["MESSAGE", "15", "id1 id2"] at time 15

  • Parse mentions: "id1" and "id2"
  • Increment user 1: ans[1] += 1ans = [1, 2, 1]
  • Increment user 2: ans[2] += 1ans = [1, 2, 2]
  • Note: User 1 gets mentioned even though they're offline (specific mentions work regardless of status)

Event 4: ["MESSAGE", "20", "ALL"] at time 20

  • Instead of updating all users now, increment lazy: lazy = 1

Step 4: Apply lazy updates

  • Add lazy = 1 to all users: ans = [2, 3, 3]

Final Result: [2, 3, 3]

  • User 0: mentioned 2 times (HERE at t=10, ALL at t=20)
  • User 1: mentioned 3 times (HERE at t=10, id1 at t=15, ALL at t=20)
  • User 2: mentioned 3 times (HERE at t=10, id2 at t=15, ALL at t=20)

Solution Implementation

1from typing import List
2
3class Solution:
4    def countMentions(self, numberOfUsers: int, events: List[List[str]]) -> List[int]:
5        """
6        Count the number of mentions for each user based on events.
7      
8        Args:
9            numberOfUsers: Total number of users in the system
10            events: List of events, each containing [event_type, timestamp, data]
11                   - "OFFLINE": User goes offline
12                   - "MESSAGE": Message event with mentions
13                   - Other events may include "ALL" mentions or "HERE" mentions
14      
15        Returns:
16            List of mention counts for each user
17        """
18        # Sort events by timestamp (as integer), then by event type priority
19        # The [2] index suggests prioritizing certain event types over others
20        events.sort(key=lambda event: (int(event[1]), event[0][2]))
21      
22        # Initialize mention count for each user
23        mention_counts = [0] * numberOfUsers
24      
25        # Track when each user will be online until (offline at timestamp + 60)
26        online_until = [0] * numberOfUsers
27      
28        # Counter for "ALL" mentions that will be applied to all users later
29        all_mentions_count = 0
30      
31        # Process each event in chronological order
32        for event_type, timestamp_str, data in events:
33            current_time = int(timestamp_str)
34          
35            # Check the first character of event type to determine action
36            if event_type[0] == "O":  # "OFFLINE" event
37                # User goes offline, but remains available for mentions for 60 time units
38                user_id = int(data)
39                online_until[user_id] = current_time + 60
40              
41            elif data[0] == "A":  # "ALL" mention
42                # Increment counter for mentions that affect all users
43                all_mentions_count += 1
44              
45            elif data[0] == "H":  # "HERE" mention
46                # Mention all users who are currently online
47                for user_id, offline_time in enumerate(online_until):
48                    if offline_time <= current_time:  # User is online
49                        mention_counts[user_id] += 1
50                      
51            else:  # Specific user mentions
52                # Parse space-separated user IDs (format: "id:X id:Y ...")
53                for mention in data.split():
54                    # Extract user ID from format "id:123"
55                    user_id = int(mention[2:])
56                    mention_counts[user_id] += 1
57      
58        # Apply "ALL" mentions to every user
59        if all_mentions_count:
60            for user_id in range(numberOfUsers):
61                mention_counts[user_id] += all_mentions_count
62              
63        return mention_counts
64
1class Solution {
2    public int[] countMentions(int numberOfUsers, List<List<String>> events) {
3        // Sort events by timestamp, and if timestamps are equal, sort by event type
4        // "MESSAGE" (M) comes before "OFFLINE" (O) based on ASCII values
5        events.sort((a, b) -> {
6            int timestampA = Integer.parseInt(a.get(1));
7            int timestampB = Integer.parseInt(b.get(1));
8            if (timestampA == timestampB) {
9                // Sort by the third character of event type to prioritize MESSAGE over OFFLINE
10                return a.get(0).charAt(2) - b.get(0).charAt(2);
11            }
12            return timestampA - timestampB;
13        });
14      
15        // Array to store mention count for each user
16        int[] mentionCounts = new int[numberOfUsers];
17      
18        // Array to track when each user will be online until (offline time + 60)
19        int[] onlineUntilTime = new int[numberOfUsers];
20      
21        // Counter for "ALL" mentions that will be applied to all users at the end
22        int allMentionsCount = 0;
23      
24        // Process each event in chronological order
25        for (List<String> event : events) {
26            String eventType = event.get(0);
27            int currentTime = Integer.parseInt(event.get(1));
28            String content = event.get(2);
29          
30            if (eventType.charAt(0) == 'O') {
31                // OFFLINE event: user goes offline but can receive mentions for 60 more time units
32                int userId = Integer.parseInt(content);
33                onlineUntilTime[userId] = currentTime + 60;
34            } else if (content.charAt(0) == 'A') {
35                // MESSAGE with "ALL": increment lazy counter to apply to all users later
36                allMentionsCount++;
37            } else if (content.charAt(0) == 'H') {
38                // MESSAGE with "HERE": mention all currently online users
39                for (int i = 0; i < numberOfUsers; i++) {
40                    if (onlineUntilTime[i] <= currentTime) {
41                        // User is online (their offline time + 60 has passed or they never went offline)
42                        mentionCounts[i]++;
43                    }
44                }
45            } else {
46                // MESSAGE with specific user IDs: parse and mention each user
47                String[] userIds = content.split(" ");
48                for (String userIdStr : userIds) {
49                    // Extract user ID from format "id123" by removing first 2 characters
50                    int userId = Integer.parseInt(userIdStr.substring(2));
51                    mentionCounts[userId]++;
52                }
53            }
54        }
55      
56        // Apply all "ALL" mentions to every user
57        if (allMentionsCount > 0) {
58            for (int i = 0; i < numberOfUsers; i++) {
59                mentionCounts[i] += allMentionsCount;
60            }
61        }
62      
63        return mentionCounts;
64    }
65}
66
1class Solution {
2public:
3    vector<int> countMentions(int numberOfUsers, vector<vector<string>>& events) {
4        // Sort events by timestamp (ascending), with offline events prioritized over messages at same time
5        ranges::sort(events, [](const vector<string>& a, const vector<string>& b) {
6            int timestampA = stoi(a[1]);
7            int timestampB = stoi(b[1]);
8          
9            // If timestamps are equal, prioritize based on event type
10            // Offline events ('O') come before Message events ('M')
11            if (timestampA == timestampB) {
12                return a[0][2] < b[0][2];  // 'O' < 'M' in ASCII
13            }
14            return timestampA < timestampB;
15        });
16
17        // Initialize result array to track mention counts for each user
18        vector<int> mentionCounts(numberOfUsers, 0);
19      
20        // Track when each user will be back online (0 means currently online)
21        vector<int> offlineUntilTime(numberOfUsers, 0);
22      
23        // Counter for "ALL" mentions that need to be distributed later
24        int pendingAllMentions = 0;
25
26        // Process each event in chronological order
27        for (const auto& event : events) {
28            string eventType = event[0];
29            int currentTime = stoi(event[1]);
30            string eventData = event[2];
31
32            if (eventType[0] == 'O') {  // OFFLINE event
33                // User goes offline for 60 time units
34                int userId = stoi(eventData);
35                offlineUntilTime[userId] = currentTime + 60;
36            } 
37            else if (eventData[0] == 'A') {  // MESSAGE event with "ALL"
38                // Increment pending mentions for all users
39                pendingAllMentions++;
40            } 
41            else if (eventData[0] == 'H') {  // MESSAGE event with "HERE"
42                // Mention only users who are currently online
43                for (int userId = 0; userId < numberOfUsers; ++userId) {
44                    if (offlineUntilTime[userId] <= currentTime) {
45                        ++mentionCounts[userId];
46                    }
47                }
48            } 
49            else {  // MESSAGE event with specific user IDs
50                // Parse space-separated user IDs (format: "id0 id1 id2...")
51                stringstream stringStream(eventData);
52                string userIdToken;
53              
54                while (stringStream >> userIdToken) {
55                    // Extract numeric ID from "idX" format (skip first 2 characters)
56                    int userId = stoi(userIdToken.substr(2));
57                    mentionCounts[userId]++;
58                }
59            }
60        }
61
62        // Apply all pending "ALL" mentions to every user
63        if (pendingAllMentions > 0) {
64            for (int userId = 0; userId < numberOfUsers; ++userId) {
65                mentionCounts[userId] += pendingAllMentions;
66            }
67        }
68
69        return mentionCounts;
70    }
71};
72
1/**
2 * Counts the number of mentions for each user based on various events
3 * @param numberOfUsers - Total number of users in the system
4 * @param events - Array of events where each event is [eventType, timestamp, data]
5 * @returns Array containing mention count for each user
6 */
7function countMentions(numberOfUsers: number, events: string[][]): number[] {
8    // Sort events by timestamp, with offline events prioritized when timestamps are equal
9    events.sort((eventA: string[], eventB: string[]): number => {
10        const timestampA: number = Number(eventA[1]);
11        const timestampB: number = Number(eventB[1]);
12      
13        if (timestampA === timestampB) {
14            // Prioritize offline events (starting with 'F') over online events
15            return eventA[0].charAt(2) < eventB[0].charAt(2) ? -1 : 1;
16        }
17        return timestampA - timestampB;
18    });
19
20    // Initialize mention counts for all users
21    const mentionCounts: number[] = Array(numberOfUsers).fill(0);
22  
23    // Track when each user will be online until (timestamp + 60 minutes)
24    const onlineUntilTimestamp: number[] = Array(numberOfUsers).fill(0);
25  
26    // Counter for mentions to all users (lazy evaluation)
27    let mentionsToAllUsers: number = 0;
28
29    // Process each event
30    for (const [eventType, timestamp, eventData] of events) {
31        const currentTimestamp: number = Number(timestamp);
32      
33        if (eventType.charAt(0) === 'O') {
34            // Online event: User comes online for 60 minutes
35            const userId: number = Number(eventData);
36            onlineUntilTimestamp[userId] = currentTimestamp + 60;
37          
38        } else if (eventData.charAt(0) === 'A') {
39            // Mention all users event
40            mentionsToAllUsers++;
41          
42        } else if (eventData.charAt(0) === 'H') {
43            // Mention all offline users (HERE event)
44            for (let userId: number = 0; userId < numberOfUsers; userId++) {
45                if (onlineUntilTimestamp[userId] <= currentTimestamp) {
46                    mentionCounts[userId]++;
47                }
48            }
49          
50        } else {
51            // Mention specific users event
52            const userMentions: string[] = eventData.split(' ');
53            for (const mention of userMentions) {
54                // Extract user ID from mention format (e.g., "id123" -> 123)
55                const userId: number = Number(mention.slice(2));
56                mentionCounts[userId]++;
57            }
58        }
59    }
60
61    // Apply lazy evaluation: add mentions to all users
62    if (mentionsToAllUsers > 0) {
63        for (let userId: number = 0; userId < numberOfUsers; userId++) {
64            mentionCounts[userId] += mentionsToAllUsers;
65        }
66    }
67
68    return mentionCounts;
69}
70

Time and Space Complexity

Time Complexity: O(m log m + m × n + L)

The time complexity breaks down as follows:

  • Sorting the events takes O(m log m) where m is the number of events
  • Processing each event:
    • For "OFFLINE" events: O(1) operation
    • For "MESSAGE" events with "ALL": O(1) operation (just incrementing lazy counter)
    • For "MESSAGE" events with "HERE": O(n) operation to check all users' online status
    • For "MESSAGE" events with specific mentions: O(k) where k is the number of mentioned users, and parsing the string takes O(l) where l is the length of that mention string
  • The final lazy application takes O(n) to update all users

In the worst case, if all events are "HERE" messages, we get O(m × n) for processing. The total length of parsing all mention strings is L. Therefore, the overall complexity is O(m log m + m × n + L).

Space Complexity: O(n)

The space complexity consists of:

  • ans array: O(n) for storing the mention count for each user
  • online_t array: O(n) for storing online timestamps for each user
  • lazy counter: O(1)
  • The sorting operation uses O(1) extra space (in-place sorting in Python's Timsort)

Therefore, the total space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Timestamp Sorting for Same-Time Events

The Pitfall: When multiple events occur at the same timestamp, the order matters significantly. The original code uses event[0][2] to sort by the third character of the event type string, which works for "OFFLINE" vs "MESSAGE" but is fragile and assumes specific string formats.

Why It's a Problem:

  • If a user goes offline and receives a "HERE" mention at the same timestamp, processing order determines whether they're counted as online
  • The sorting key event[0][2] relies on implementation details (3rd character being 'F' for OFFLINE and 'S' for MESSAGE)
  • Future event types or format changes could break this logic

Solution:

def countMentions(self, numberOfUsers: int, events: List[List[str]]) -> List[int]:
    # More explicit sorting with clear priority
    def event_priority(event):
        timestamp = int(event[1])
        # Explicitly define priority: OFFLINE events process first (priority 0)
        # MESSAGE events process second (priority 1)
        priority = 0 if event[0] == "OFFLINE" else 1
        return (timestamp, priority)
  
    events.sort(key=event_priority)

2. Misunderstanding Online/Offline Status Logic

The Pitfall: The variable name online_until in the code comments is misleading. The array actually tracks when users come back online (timestamp + 60), not when they're online until.

Why It's a Problem:

  • A user is considered online when online_t[i] <= current_time
  • This means online_t stores the "next time they'll be online" not "online until"
  • Initially all values are 0, meaning all users start online (since 0 <= any timestamp)

Solution:

def countMentions(self, numberOfUsers: int, events: List[List[str]]) -> List[int]:
    # Better variable naming and documentation
    next_online_time = [0] * numberOfUsers  # Time when user comes back online (0 = currently online)
  
    # When processing OFFLINE event:
    if event_type == "OFFLINE":
        user_id = int(data)
        next_online_time[user_id] = current_time + 60  # User offline for 60 units
  
    # When checking if user is online:
    elif data == "HERE":
        for user_id in range(numberOfUsers):
            # User is online if current time >= their next online time
            if next_online_time[user_id] <= current_time:
                mention_counts[user_id] += 1

3. Not Handling Multiple Mentions of Same User in One Message

The Pitfall: While the code correctly handles this, developers often overlook that "id0 id0 id0" should count as 3 mentions for user 0, not 1.

Why It's a Problem:

  • Natural instinct might be to use a set to track unique users mentioned
  • The problem explicitly states duplicates count as separate mentions

Incorrect Approach:

# WRONG: Using a set would miss duplicate mentions
mentioned_users = set()
for mention in data.split():
    user_id = int(mention[2:])
    mentioned_users.add(user_id)
for user_id in mentioned_users:
    mention_counts[user_id] += 1  # Only counts once per message

Correct Approach (as in original code):

# CORRECT: Count each mention separately
for mention in data.split():
    user_id = int(mention[2:])
    mention_counts[user_id] += 1  # Counts every occurrence
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More