40. Peak Concurrent Sessions per User Group
Origin: HackerRank Prep Kit #40
题目描述
给定一组事件,每个事件是 [timestamp, user_id, group_id, event_type],event_type 为 “login” 或 “logout”。计算每个 group 的峰值并发会话数。返回 [group_id, peak_count] 数组。
示例
Example
Input:
events = [
['1', '101', '500', 'login'],
['2', '102', '500', 'login'],
['5', '101', '500', 'logout'],
['6', '102', '500', 'logout']
]
Output: [[500, 2]]
Explanation: group 500 在时间 2 时有两个并发会话(user 101 和 102 都在线),峰值 = 2。
Sample Input 0
2 4 1 101 500 login 2 101 500 logout
Output: 500 1
Sample Input 1
2 4 5 123 700 login 5 123 700 logout
Output: 700 1
约束
- 0 <= events.length <= 100000
- 0 <= timestamp <= 10
- 1 <= user_id <= 10
- 1 <= group_id <= 10
- event_type ∈ {“login”, “logout”}
- 事件可能未按时间戳排序
- 多个事件可能共享同一时间戳
- 用户可在不同 group 有重叠会话
- 每个 logout 都有对应的 login
关键信息
- 按时间排序后,login +1,logout -1,维护每个 group 的当前计数
- 同一时间戳:logout 优先于 login?还是 login 优先?需要确认
- 返回每个 group 的峰值
Resolution
我的解法
function computeGroupPeakConcurrency(events: string[][]): number[][] {
if (!events.length) return []
const groupMap = new Map()
const groupMaxMap = new Map()
const parsed = events.map(e => ({
timestamp: Number(e[0]),
userId: e[1],
groupId: Number(e[2]),
event: e[3]
}))
parsed.sort((a, b) => {
const timeDiff = a.timestamp - b.timestamp
if (timeDiff !== 0) return timeDiff
return a.event === 'login' ? -1 : 1
})
for (let i = 0; i < parsed.length; i++) {
const { groupId, event } = parsed[i]
if (!groupMap.has(groupId)) {
groupMap.set(groupId, 0)
groupMaxMap.set(groupId, groupMap.get(groupId))
}
if (event === 'login') {
groupMap.set(groupId, groupMap.get(groupId) + 1)
groupMaxMap.set(groupId, Math.max(groupMaxMap.get(groupId), groupMap.get(groupId)))
} else {
groupMap.set(groupId, groupMap.get(groupId) - 1)
}
}
const result: number[][] = []
for (const [key, value] of groupMaxMap) {
result.push([key, value])
}
return result.sort((a, b) => Number(a[0]) - Number(b[0]))
}解题思路
扫描线问题。三步走:
- 把字符串字段转成 number(groupId 必须转,否则 Map key 和输出类型不对)
- 按时间戳排序,同一时间戳 login 优先(login 排在 logout 前面)
- 遍历事件,login +1,logout -1,记录每个 group 的峰值
踩坑
- 字符串 vs 数字:groupId 没转 number,Map 用字符串做 key,输出也是字符串,HackerRank 期望数字类型。这是最大的 bug。
- 同一时间戳排序方向:login 优先(login 排在 logout 前面),否则同一时刻的 logout 先处理会虚低
- groupMap 初始化:第一次遇到 groupId 时要先初始化为 0,不能只在 login 时初始化
- truthy 判断:count 为 0 时是 falsy,不能用
if (groupMap.get(groupId))判断,必须用has() - 题目和用例不一致:题目说 “output order does not matter” 但实际必须按 group_id 排序;说 “每个 logout 有对应 login” 但用例里出现了无对应 login 的 logout。遇到先 logout 的 group,count 记 -1 但 peak 记 0(不减成负数)
复杂度
- 时间:O(n log n),排序
- 空间:O(n),Map 存储