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 mentionsALL
: Mentions every single user in the systemHERE
: 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.
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.
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 useronline_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 EvaluatorExample 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):
["MESSAGE", "10", "HERE"]
- timestamp 10["OFFLINE", "15", "1"]
- timestamp 15 (comes before MESSAGE at 15)["MESSAGE", "15", "id1 id2"]
- timestamp 15["MESSAGE", "20", "ALL"]
- timestamp 20
Step 2: Initialize data structures
ans = [0, 0, 0]
- mention counts for users 0, 1, 2online_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] += 1
→ans = [1, 2, 1]
- Increment user 2:
ans[2] += 1
→ans = [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)
wherem
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)
wherek
is the number of mentioned users, and parsing the string takesO(l)
wherel
is the length of that mention string
- For "OFFLINE" events:
- 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 useronline_t
array:O(n)
for storing online timestamps for each userlazy
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
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!