京东9月19日机考笔试 风控AUC排序题解
- 轮次
- 笔试
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- 编程题(风控排序评估):给定 m 笔交易的真实标签 t_i(0 正常、1 欺诈)和模型风险分 s_i(越大越像欺诈),按 Mann-Whitney U 的秩统计计算 AUC,分数相同的交易必须取平均名次,输出保留 6 位小数。样例输入 m=5、标签 0 1 1 0 1、分数 1.0 2.0 2.0 3.0 4.0,输出 0.666667。
《参考解析》
平均名次怎么算:把交易按风险分从小到大排序,名次从 1 起。若连续若干笔分数相同、占住第 L 到第 R 个位置,则这几笔都记 (L+R)/2。实现上排序后单次扫描,遇到同分就把区间一次扩满,再把区间内所有元素写同一个平均名次。样例里两个 2.0 占第 2、3 位,各记 2.5。最常见的错是按出现顺序硬拆成 1、2、3,这等于人为规定并列样本有先后,平行样本被算成「一对一错」,AUC 会偏。
从正类名次和还原 AUC:记欺诈笔数 k、正常笔数 g、欺诈样本名次和 S。如果所有欺诈都排在最前面,名次和最小是 1+2+…+k = k(k+1)/2;多出来的 V = S − k(k+1)/2 就是正类「赢过」负类的对数,其中并列各计 0.5。正负样本一共 k·g 对,所以 AUC = V/(k·g)。这也正是 AUC 的概率解释:随机取一正一负,正类分数更高的概率。
四类常见假解:一是并列按顺序硬拆名次;二是名次从 0 起算,导致减去的最小名次和错位;三是按分数从大到小排序却仍套同一个公式(等价于算 1−AUC);四是只统计严格大于、并列不计 0.5。另外要顺手处理除零:k 或 g 为 0 时(全正或全负)直接返回 0,不要让浮点异常把样例之外的测试点带崩。同分比较用读入后的 double 直接 == 即可(相同字面量解析结果一致),但别用 1e-9 之类的近似阈值,那会把本来不相等的分数并到一起。
复杂度与实现:排序是唯一的大头,时间 O(m log m)、空间 O(m),m 到 1e6 也稳。参考实现:
def auc(labels, scores):
m = len(labels)
order = sorted(range(m), key=lambda i: scores[i])
rank = [0.0] * m
i = 0
while i < m: # 把同分区间一次扩满
j = i
while j + 1 < m and scores[order[j + 1]] == scores[order[i]]:
j += 1
avg = (i + 1 + j + 1) / 2.0 # 名次从 1 起,区间 [i+1, j+1]
for t in range(i, j + 1):
rank[order[t]] = avg
i = j + 1
k = sum(labels)
if k == 0 or k == m:
return 0.0
s = sum(rank[i] for i in range(m) if labels[i] == 1)
v = s - k * (k + 1) / 2.0
return v / (k * (m - k))
输出用 %.6f(C/C++ 的 setprecision(6)),注意别用默认精度把 0.666667 打成 0.66667。