← 开发笔记

Java 用姓名频次找出未完成比赛的选手

要找出未完成比赛的选手,需要按姓名统计人数,而非只检查姓名是否存在。将完赛频次从参赛频次中扣除;题目保证最终恰好只有一个姓名的剩余人数为 1。

输入约束允许最多 100,000 名参赛者,完赛者少一人,并允许重名。姓名由 1–20 个小写英文字母组成。假设完赛名单是从有效参赛名单中移除一人后得到的。

处理流程

统计每位参赛者,逐一扣除完赛者,再返回剩余人数为一的姓名。

统计每位参赛者,逐一扣除完赛者,再返回剩余人数为一的姓名。

保存人数差

将完整类保存为 Solution.java。原文中的代码已按 Java 11 目标编译,并在 JDK 17 上执行。

import java.util.HashMap;
import java.util.Map;

public class Solution {
    public String solution(String[] participant, String[] completion) {
        Map<String, Integer> counts = new HashMap<>();
        for (String name : participant) counts.merge(name, 1, Integer::sum);
        for (String name : completion) counts.put(name, counts.get(name) - 1);
        for (Map.Entry<String, Integer> entry : counts.entrySet()) {
            if (entry.getValue() == 1) return entry.getKey();
        }
        throw new IllegalArgumentException("Expected exactly one missing runner");
    }
}

merge(name, 1, Integer::sum) 将新姓名的计数设为 1,并递增已有姓名的计数。每位完赛者使相应计数减一。参赛者为 [ana, bob, ana],完赛者为 [bob, ana] 时,ana 剩余 2 - 1 = 1。

Set 差集会丢失有两人名叫 ana 这一信息。计数保留重复次数,从而表示一个人未完赛。改变完赛数组的顺序不会影响结果。

不依赖 HashMap 的顺序

HashMap不保证迭代顺序。能够返回第一个正计数项,是因为有效输入只会留下一个这样的项,而非因为迭代有特定顺序。代码返回剩余计数为 1 的姓名。

最后的异常并不构成完整的输入验证。通用对账 API 还需要针对未知完赛者或多名未完赛者制定额外策略并实施检查。

姓名长度最多为 20,且假设哈希操作平均为常数时间,则时间复杂度为 O(n),u 个不同姓名的额外空间为 O(u)。没有基准测试证明流或循环在所有情况下更快。

如果只关心不同的种类,Set 就足够了;可对照 Ponketmon 题目的选择上限。

讨论