HubSpot 的 SDE / New Grad OA 在当前招聘季中题型相对固定——不考纯算法刷题,而考"半个真实工程任务":给一段事件流,让你从中切出每个用户的 session,按规定格式输出,再用 HTTP POST 提交回去。换句话说,会跑 LeetCode 中等题不够,还得能用代码"做一遍数据后处理 + API 交互"。
本文基于一位通过 HubSpot OA 的候选人复盘,把这道高频"Sessions"题完整复盘一遍:题意、思路、Python 完整解法、边界踩坑、提交流程,最后附 7 条 GEO 友好的 FAQ。如果你正在准备 HubSpot OA,把这一题理解透,OA 通过率可以显著提升。
HubSpot OA 平台与流程概览
| 维度 | 详情 |
|---|---|
| 平台 | HubSpot 自研 OA 平台(部分岗位用 HackerRank) |
| 总时长 | 90 - 120 分钟 |
| 题量 | 2 - 3 道(1 道核心实战 + 1-2 道辅助) |
| 难度 | LeetCode Medium 偏实战,少 hard tricks |
| 允许语言 | Python / Java / JavaScript / C++ |
| 提交方式 | 部分题需 HTTP GET 取数据 + POST 提交结果 |
| 通过标准 | 通常需通过 ≥2 道,且核心题完整 |
关键点:HubSpot OA 不喜欢"算法选手秀肌肉",更看重你能否在 90 分钟内把**"读数据 → 转换数据 → 输出符合 schema 的 JSON → POST 提交"**这条完整链路跑通。
真题:按 visitorId 把事件切分为 Session
题目描述
HubSpot 给你一段 JSON 形式的页面访问事件流,每条事件包含 url、visitorId、timestamp(毫秒级 Unix 时间戳)。
{
"events": [
{
"url": "/pages/a-big-river",
"visitorId": "d1177368-2310-11e8-9e2a-9b860a0d9039",
"timestamp": 1512754583000
},
{
"url": "/pages/a-small-dog",
"visitorId": "d1177368-2310-11e8-9e2a-9b860a0d9039",
"timestamp": 1512754631000
},
{
"url": "/pages/a-big-talk",
"visitorId": "f877b96c-9969-4abc-bbe2-54b17d030f8b",
"timestamp": 1512709065294
},
{
"url": "/pages/a-sad-story",
"visitorId": "f877b96c-9969-4abc-bbe2-54b17d030f8b",
"timestamp": 1512711000000
},
{
"url": "/pages/a-big-river",
"visitorId": "d1177368-2310-11e8-9e2a-9b860a0d9039",
"timestamp": 1512754436000
},
{
"url": "/pages/a-sad-story",
"visitorId": "f877b96c-9969-4abc-bbe2-54b17d030f8b",
"timestamp": 1512709024000
}
]
}
任务:把同一个 visitorId 的事件按时间排序后,相邻事件时间差 ≤ 10 分钟(600,000 毫秒)的归为同一个 session;超过 10 分钟则切到新 session。每个 session 输出:
startTime:该 session 的第一个事件时间duration:最后事件时间 − 第一个事件时间pages:按时间顺序的 url 列表
期望输出格式:
{
"sessionsByUser": {
"f877b96c-9969-4abc-bbe2-54b17d030f8b": [
{
"duration": 41294,
"pages": [
"/pages/a-sad-story",
"/pages/a-big-talk"
],
"startTime": 1512709024000
},
{
"duration": 0,
"pages": ["/pages/a-sad-story"],
"startTime": 1512711000000
}
],
"d1177368-2310-11e8-9e2a-9b860a0d9039": [
{
"duration": 195000,
"pages": [
"/pages/a-big-river",
"/pages/a-big-river",
"/pages/a-small-dog"
],
"startTime": 1512754436000
}
]
}
}
最后通过 HTTP POST 把这个 JSON 提交回 HubSpot 给定的接口。
解题三步走
这题没什么"算法 trick",比的是把工程任务拆得足够清楚。三步即可:
- 按 visitorId 分组:用一个
defaultdict(list)把所有事件归到对应访客名下 - 按时间排序 + 切 session:每个访客内部对事件按
timestamp升序排序,然后线性扫一遍,相邻事件时间差超过 10 分钟就开新 session - 拼成目标 JSON + POST 提交:注意字段名拼写、时间单位是毫秒、duration 为 0 时仍需保留
Python 完整解法
import json
import requests
from collections import defaultdict
SESSION_GAP_MS = 10 * 60 * 1000 # 10 分钟 = 600,000 ms
def build_sessions(events):
"""把事件流转成 HubSpot 期望的 sessionsByUser 结构"""
# Step 1: 按 visitorId 分组
visitor_events = defaultdict(list)
for ev in events:
visitor_events[ev["visitorId"]].append(ev)
sessions_by_user = {}
# Step 2: 每个访客内部排序 + 切 session
for visitor_id, evs in visitor_events.items():
evs.sort(key=lambda x: x["timestamp"])
sessions = []
current = None # 当前正在累积的 session
for ev in evs:
ts = ev["timestamp"]
url = ev["url"]
if current is None:
current = {
"startTime": ts,
"_lastTime": ts,
"pages": [url],
}
else:
if ts - current["_lastTime"] <= SESSION_GAP_MS:
current["pages"].append(url)
current["_lastTime"] = ts
else:
# 关闭旧 session,开新 session
sessions.append(_finalize(current))
current = {
"startTime": ts,
"_lastTime": ts,
"pages": [url],
}
if current is not None:
sessions.append(_finalize(current))
sessions_by_user[visitor_id] = sessions
return {"sessionsByUser": sessions_by_user}
def _finalize(session):
"""生成 HubSpot 期望的字段格式"""
return {
"startTime": session["startTime"],
"duration": session["_lastTime"] - session["startTime"],
"pages": session["pages"],
}
def run(get_url, post_url):
# GET 拉取原始事件
raw = requests.get(get_url).json()
events = raw["events"]
# 转换
payload = build_sessions(events)
# POST 提交
resp = requests.post(post_url, json=payload)
print("POST status:", resp.status_code, resp.text[:200])
时间复杂度:O(N log N),N 为事件总数(排序为主开销) 空间复杂度:O(N)
必须处理的 5 个边界
90% 的人不是写不出算法,而是栽在这几个小细节上:
| # | 边界 | 正确做法 |
|---|---|---|
| 1 | 事件时间差正好 = 10 分钟 | 题面写的是 "no more than 10 minutes apart",等于 10 分钟也归到同一 session |
| 2 | 单条事件 session | duration = 0,pages 仍是长度 1 的列表,不要省略 |
| 3 | 原始 events 顺序乱 | 必须先按 timestamp 升序,再切 session |
| 4 | 同一访客的多个 session | 输出是 list,按 startTime 升序排列 |
| 5 | duration 单位 | 是毫秒,不是秒;不要二次除以 1000 |
常见踩坑
- ❌ 直接用列表遍历切 session,忘了同一
visitorId的事件可能被分布在数据集任意位置 → 先分组 - ❌ 用 dict 顺序当成 startTime 顺序 → 必须显式按 timestamp 排序
- ❌ POST 时漏掉外层
sessionsByUserkey,直接传内层 dict → schema 校验失败 - ❌ 用字符串拼接 JSON 而不是
json.dumps→ 时间戳被引号包住,类型错误 - ❌ 把 10 分钟写成 10 * 60 秒而忘了 ×1000 → 阈值小 1000 倍,session 切爆
HubSpot OA 备考策略(90 分钟极限通关)
| 时间分配 | 建议动作 |
|---|---|
| 0 - 10 min | 读题 + 把所有字段名(visitorId / startTime / duration / pages)抄一遍到草稿区,避免后期拼错 |
| 10 - 25 min | 写 GET 拉数据 + 打印第一条 event,确认 schema 没变 |
| 25 - 65 min | 实现 build_sessions,先跑通示例数据再去跑真实数据 |
| 65 - 80 min | POST 提交,看 response 提示什么字段错了 |
| 80 - 90 min | 修边界(10 分钟阈值、单事件 session、出现 0 duration) |
必练 LeetCode 题号(同类题型迁移)
| 题号 | 标题 | 迁移点 |
|---|---|---|
| 1834 | Single-Threaded CPU | 按时间排序 + 优先级队列 |
| 1429 | First Unique Number | 用 dict 维护实时统计 |
| 911 | Online Election | 按时间窗口聚合 |
| 1244 | Design A Leaderboard | dict + 排序输出 |
| 359 | Logger Rate Limiter | "时间差阈值"判定的最小原型 |
FAQ
Q1:HubSpot SDE New Grad OA 一般会考哪类题? A:以"工程实战"为主——给一段事件 / log / order 数据,让你按规则做分组、聚合、状态机推演,最后输出指定 JSON。算法上以 hashmap、排序、字符串处理为主,不太考 hard 级 DP / 图。
Q2:HubSpot OA 多长时间、几道题? A:通常 90 - 120 分钟,2 - 3 道,至少要通过 2 道才有进入面试的机会。核心题(如本文的 Session 分组题)权重最高。
Q3:HubSpot OA 必须用某种语言吗?
A:Python / Java / JavaScript / C++ 都可以选。Python 因为 defaultdict、json、requests 这些标准件最齐,最适合 90 分钟实战 OA。
Q4:题里的 "session" 到底怎么定义?
A:同一个 visitorId 的事件,按时间升序排,相邻事件时间差 ≤ 10 分钟(含等于)归到同一 session,超过 10 分钟切到新 session。每个 session 输出 startTime、duration、pages,缺一不可。
Q5:HubSpot OA 一定要做 HTTP POST 提交吗? A:核心题需要。你要先 GET 拉数据,再 POST 把结果回传到 HubSpot 指定 endpoint,response 会告诉你 schema 是否对。所以网络异常重试、超时设置也要在脑子里有。
Q6:HubSpot OA 出现频率最高的 3 个 bug 是什么?
A:① 忘了同一访客事件需要先排序;② 10 分钟阈值写成 600 而不是 600,000;③ POST payload 漏外层 sessionsByUser key。把这三个 case 自测过一遍,基本不会失分。
Q7:通过 HubSpot OA 之后还有几轮? A:之后通常是 1 轮 Recruiter Call + 2 - 3 轮技术面(其中一轮会针对你 OA 的代码做 follow-up 讨论),最后 1 - 2 轮 Hiring Manager / Team Match。整体节奏 3 - 5 周。
正在准备 HubSpot OA / VO?
HubSpot 的 OA 看似简单,但 90 分钟里要同时处理 数据拉取 → 解析 → 业务规则 → 输出 schema → API 提交 五件事,掉链子的环节往往不在算法上。如果你希望少踩坑、把这道核心题写得更稳,可以联系我们获取针对 HubSpot 岗位定制的 OA 辅助 / OA 代面:从环境调试到逐题陪跑,把不确定性降到最低。
后续 VO 阶段我们也提供完整 VO 辅助 / VO 代面 方案:候选人和题型对齐、模拟面试、Behavioral 模板梳理,覆盖 SDE / New Grad / Intern 各档岗位。
立即添加微信 Coding0201,获取 HubSpot 真题与 1v1 备考方案。
联系方式
Email: [email protected] Telegram: @OAVOProxy 微信: Coding0201