1. 概述

根据 Wikipedia 的定义,变位词(anagram) 是指通过重新排列一个单词或短语的字母,形成另一个单词或短语。

我们可以把这个概念扩展到字符串处理中:如果两个字符串包含完全相同的字符及其出现次数,只是顺序不同,那么它们互为变位词

本文将探讨如何判断两个字符串是否为全字符匹配的变位词 —— 包括空格、标点、数字等非字母字符。例如,"!low-salt!""owls-lat!!" 就是变位词,因为它们包含完全相同的字符和频次。

✅ 目标:判断两字符串是否互为变位词(字符频次完全一致)
❌ 不是:仅字母重排、忽略大小写或符号(除非特别处理)


2. 解法概览

我们来对比几种判断变位词的常见方法。所有方案都会先做一步快速校验:

⚠️ 如果两个字符串长度不同,直接返回 false —— 长度不等不可能是变位词。

我们会从两个维度评估每种方案:

  • 开发复杂度:代码是否简洁、易维护
  • ⚙️ 时间复杂度:使用大 O 表示法分析性能

3. 排序法判断变位词

思路很简单:对两个字符串的字符排序,若结果相同,则为变位词

Java 实现步骤:

  1. 转为 char[]
  2. 使用 Arrays.sort() 排序
  3. Arrays.equals() 判断是否相等
boolean isAnagramSort(String string1, String string2) {
    if (string1.length() != string2.length()) {
        return false;
    }
    char[] a1 = string1.toCharArray();
    char[] a2 = string2.toCharArray();
    Arrays.sort(a1);
    Arrays.sort(a2);
    return Arrays.equals(a1, a2);
}

优缺点分析

  • ✅ 实现简单直观,适合快速编码
  • ⚠️ 时间复杂度为 **O(n log n)**,瓶颈在排序
  • 💾 需要额外空间存储两个字符数组

👉 适合对性能要求不高、追求可读性的场景。简单粗暴,但不是最优解。


4. 计数法(字符频次统计)

核心思想:统计每个字符出现次数,若两字符串的“字符频次直方图”一致,则为变位词

优化技巧:只用一个计数数组:

  • 遍历第一个字符串:字符计数 ++
  • 遍历第二个字符串:字符计数 --
  • 最终所有计数应为 0,否则不是变位词

假设字符集为 ASCII(0~255),可用长度为 256 的数组:

private static int CHARACTER_RANGE = 256;

public boolean isAnagramCounting(String string1, String string2) {
    if (string1.length() != string2.length()) {
        return false;
    }
    int count[] = new int[CHARACTER_RANGE];
    for (int i = 0; i < string1.length(); i++) {
        count[string1.charAt(i)]++;
        count[string2.charAt(i)]--;
    }
    for (int i = 0; i < CHARACTER_RANGE; i++) {
        if (count[i] != 0) {
            return false;
        }
    }
    return true;
}

优缺点分析

  • ✅ 时间复杂度为 **O(n)**,性能优秀
  • 💾 空间复杂度固定为 O(256),对 ASCII 来说可以接受
  • ⚠️ 若扩展到 UTF-8(如中文),CHARACTER_RANGE 会剧增,内存开销大
  • ❌ 代码略繁琐,依赖手动管理数组

👉 适合字符集有限、追求极致性能的场景,比如处理大量英文文本。


5. 使用 Guava 的 MultiSet

想避免手动计数?可以用 Guava 提供的 Multiset —— 支持重复元素、顺序无关的集合。

{a, a, b}{b, a, a} 被认为是相等的 Multiset

引入依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

实现代码

boolean isAnagramMultiset(String string1, String string2) {
    if (string1.length() != string2.length()) {
        return false;
    }
    Multiset<Character> multiset1 = HashMultiset.create();
    Multiset<Character> multiset2 = HashMultiset.create();
    for (int i = 0; i < string1.length(); i++) {
        multiset1.add(string1.charAt(i));
        multiset2.add(string2.charAt(i));
    }
    return multiset1.equals(multiset2);
}

优缺点分析

  • ✅ 时间复杂度 **O(n)**,性能好
  • ✅ 不需要预设字符范围,自动扩容,适合 Unicode
  • ✅ 利用高级集合类,代码更简洁、可读性强
  • ⚠️ 引入第三方依赖(Guava),增加项目复杂度

👉 推荐在已使用 Guava 的项目中使用,开发效率高,不易踩坑。


6. 字母级变位词(忽略大小写与符号)

前面的例子是“全字符匹配”,但实际语言中,我们更关心字母重排,比如:

“A decimal point” ↔ “I’m a dot in place.”

这种情况下,我们只关心字母(忽略大小写),空格、标点、引号都不计入。

解决方案:预处理 + 通用判断

先清洗字符串:只保留字母并转小写,再用前面任一算法判断。

String preprocess(String source) {
    return source.replaceAll("[^a-zA-Z]", "").toLowerCase();
}

boolean isLetterBasedAnagramMultiset(String string1, String string2) {
    return isAnagramMultiset(preprocess(string1), preprocess(string2));
}

扩展性

  • ✅ 若想包含数字:[^a-zA-Z0-9]
  • ✅ 若区分大小写:去掉 .toLowerCase()
  • ✅ 可复用已有判断逻辑,结构清晰

👉 这是处理“语言学意义上变位词”的标准做法,建议封装成工具方法。


7. 总结

方法 时间复杂度 空间复杂度 优点 缺点
排序法 O(n log n) O(n) 简单直观 性能较差
计数数组 O(n) O(1)(固定256) 快速高效 不适合大字符集
Guava Multiset O(n) O(k)(k为唯一字符数) 灵活、易读 依赖外部库
预处理 + 判断 O(n) 视实现而定 适应业务需求 需额外清洗

建议

  • 🔹 快速原型 or 小数据:用排序法,简单不踩坑
  • 🔹 高性能要求、ASCII 文本:计数数组,极致优化
  • 🔹 项目已用 GuavaMultiset,代码清爽
  • 🔹 语言学场景:预处理 + 上述任一方案

所有示例代码已上传至 GitHub:https://github.com/example/string-anagram-demo


原始标题:Check if Two Strings Are Anagrams in Java