另一种方法是计算每个字符串中每个字符的数量并比较计数。一个简单的实现应该花费O(max(N, A)) 时间,其中 N 是较大字符串的长度,A 是用于存储计数的数组的大小。例如,在 Java 中:
public boolean equalIgnoringOrder(String s1, String s2) {
if (s1.length() != s2.length()) {
return false;
}
// Assuming characters in the range ASCII 0 to 127
int[] c1 = new int[128];
int[] c2 = new int[128];
for (int i = 0; i < s1.length(); i++) {
c1[s1.charAt(i)]++;
c2[s2.charAt(i)]++;
}
for (int i = 0; i < c1.length; i++) {
if (c1[i] != c2[i]) {
return false;
}
}
return true;
}
对此有一些可能的改进。例如,您可以通过缩小范围来处理任意字符集;即通过s1 和s2 进行初始传递,寻找每个字符中的最小和最大字符,并使用它来确定c1 和c2 的大小以及基本偏移量。这将平均使用更少的空间并减少初始化计数数组的时间。它还为比较提供了短路;例如当s1 和s2 的最小和最大字符不相同时。
相比之下,比较使用堆排序或快速排序排序的字符串平均为O(NlogN) 与O(N) 空间,其中N 是较大字符串的长度。
但是,正如@pst 指出的那样,如果 N 不大,比例常数可以使 O(NlogN) 甚至 O(N*N) 算法优于 O(N) 算法。在这种情况下,被比较的字符串的平均长度可能是最重要的因素。
上面的代码有效地执行了带有几个短路的基数排序。 (如果包括与范围缩小相关的短路,则为三个。)因此最终归结为快速排序/堆排序或基数排序是否更好。这取决于输入字符串的长度和字符范围。
采取不同的策略。 @John 的回答建议我们计算素数的乘积。如果我们使用任意精度表示进行计算,则结果值对于每个不同的“相等忽略顺序”字符串集都是唯一的。不幸的是,计算将是O(N*N)。 (每个中间产品都有O(N) 数字,将一个N 位数字乘以一个常数是O(N)。对N 个字符执行此操作,您将得到O(N*N)。)
但是如果我们以 64 为模进行计算,则结果实际上是一个非常好的散列,它对字符顺序不敏感;例如
long hash = 1;
for (int i = 0; i < s.length(); i++) {
hash = hash * primes[s.charAt(i)];
}
因此,我认为在比较随机生成的字符串时平均提供最佳性能和空间使用率的算法可能是以下形式:
if (s1.length() != s2.length()) {
return false;
}
if (hash(s1) != hash(s2)) { // computed as above
return false;
}
// Compare using sorting or character counting as above.
最后一点。如果我们假设字符串指针不相同并且字符串的长度不相等,那么计算此equals 谓词的任何算法都必须为O(N) 或更糟。它必须检查两个字符串中的每个字符才能做出决定,这需要O(N) 操作。
在这种情况下,任何算法对获取的值执行少于2 * N fetches 或少于2 * N 的进一步操作都可证明是不正确的。