题目描述

✅ 929. 独特的电子邮件地址

image-20260929105221933

image-20260929105222131

题意分析

邮箱由 @ 前的本地名与 @ 后的域名组成。题目只允许对本地名应用两条规则:忽略点号,忽略第一个加号及其后面的内容。域名保持原样,最后统计这些地址实际对应多少个不同目标。

解法:规范化 + 哈希集合去重

核心思路

[!blue]

先定位 @,把本地名和域名分开,防止将局部规则误用到整个地址。随后从左到右扫描本地名:遇到 . 只跳过当前字符,遇到 + 则结束扫描,其他字符按顺序放入缓冲区。

扫描过程中,缓冲区恰好保存已经处理的本地名前缀中仍有意义的字符;一旦遇到第一个 +,其后的所有本地名字符都不再影响目标,因此可以直接停止。最后拼回规范本地名、@ 和原域名,得到该地址的唯一规范表示。

两个地址按题目规则指向同一目标,当且仅当它们的规范本地名相同、域名也相同。保留中间的 @ 能明确区分这两部分,所以用整个规范地址作为集合键,既会合并等价地址,也不会合并不同目标。集合大小就是答案。

解题步骤

  1. 找到 @ 并分开本地名与域名。
  2. 扫描本地名,跳过点号,在首个加号处停止。
  3. 拼回规范本地名、@ 和原域名。
  4. 加入集合并返回集合大小。

本地名没有加号或点号时,扫描会完整保留它;多个加号只需处理第一个。题目保证地址格式有效,代码据此定位唯一的 @ 并取出两部分。

代码实现

class Solution {
    public int numUniqueEmails(String[] emails) {
        Set<String> set = new HashSet<>();

        for (String email : emails) {
            // 先分离本地名与域名,忽略规则只作用于本地名。
            int at = email.indexOf('@');
            String local = email.substring(0, at);
            String domain = email.substring(at + 1);

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < local.length(); i++) {
                char c = local.charAt(i);

                // 加号及后面内容全部忽略,点号则仅跳过自身。
                if (c == '+') {
                    break;
                }

                if (c == '.') {
                    continue;
                }

                sb.append(c);
            }

            set.add(sb + "@" + domain);
        }

        return set.size();
    }
}
func numUniqueEmails(emails []string) int {
    set := make(map[string]bool)
    for _, email := range emails {
        // 先分离本地名与域名,忽略规则只作用于本地名。
        at := 0
        for at < len(email) && email[at] != '@' {
            at++
        }
        local := email[:at]
        domain := email[at+1:]

        buf := make([]byte, 0, len(local))
        for i := 0; i < len(local); i++ {
            c := local[i]
            // 加号及后面内容全部忽略,点号则仅跳过自身。
            if c == '+' {
                break
            }
            if c == '.' {
                continue
            }
            buf = append(buf, c)
        }
        set[string(buf)+"@"+domain] = true
    }
    return len(set)
}

复杂度分析

  • 时间复杂度:期望 $O(S)$,S 为输入地址总字符数。
  • 空间复杂度:$O(S)$,保存规范地址与临时缓冲。

关键点总结

[!green]

  • 两条忽略规则只作用于本地名。
  • 加号表示截断,点号表示跳过,两种操作不同。
  • 规范化后使用完整地址去重。

易错点总结

[!yellow]

  • 修改域名中的点号,会合并本来不同的域名。
  • 加号表示截断整个后缀,点号只表示跳过自身,不能交换处理方式。
  • 拼接时省略 @ 会丢失两部分边界,使不同的本地名与域名组合产生同一个键。

相似题目

题目 难度 关联与区别
71. 简化路径 中等 同样按规则规范化字符串后判等,本题只处理邮箱本地名的点号和加号,不能把规则扩展到域名。
609. 在系统中查找重复文件 中等 同样先求等价内容键再用集合或映射聚合,本题键是规范化邮箱,原题键是文件内容。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2020/69294617
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!