Robert Sedgewick和Kevin Wayne所著的阅读算法第四版,我发现了以下问题:
散列攻击:假设String的hashCode()实现如下,则找到2个^ N个字符串,每个字符串的长度为2 ^ N个,具有相同的hashCode()值:
public int hashCode() {
int hash = 0;
for (int i = 0; i < length(); i++)
hash = (hash * 31) + charAt(i);
return hash;
}
强烈提示:AA和BB的值相同。
我想到的是生成所有可能的长度为2 ^ N的字符串,并比较它们的hashCodes。但是,这对于大的N来说非常昂贵,我怀疑这是正确的解决方案。 你能给我一些暗示,我想念整个图片吗?
答案 0 :(得分:3)
安德烈亚斯(Andreas)和格兰斯(Glains)的答案都是正确的,但是如果您的目标是产生2个 N 个长度为2 < i> N 。
相反,一种更简单的方法是构建仅由Aa
和BB
的串联序列组成的字符串。对于2×1的长度,您有{Aa
,BB
};对于长度2×2,您有{AaAa
,AaBB
,BBAa
,BBBB
},对于长度2×3,您有{{AaAaAa
,{{1 }},AAaaBB
,AaBBAa
,AaBBBB
,BBAaAa
,BBAaBB
,BBBBAa
};等等。
(注意:您引用的文字是说字符串的长度应为2 N 。我猜您引用不正确,而实际上是在要求长度2 N ;但是如果确实要求长度2 N ,则可以在继续操作时简单地删除元素。)
答案 1 :(得分:1)
“强提示”说明。
强烈提示:AA和BB的值相同。
在ASCII / Unicode中,B
的值比A
高1。由于这些是倒数第二个字符,因此将值乘以31
,因此,当您将31
更改为xxxxAa
时,哈希码将增加xxxxBa
。
要对此进行补偿,您需要将最后一个字符偏移-31
。由于小写字母比大写字母高32,因此将a
更改为A
就是-32
,然后将一个字母更改为B
就是-31
。
因此,它获得相同的哈希码,将倒数第二个字母更改为下一个字母(例如,将A
更改为B
,并将最后一个字母从小写字母更改为下一个大写字母(例如a
到B
)。
您现在可以使用该提示以相同的哈希码生成多达26个字符串。
答案 2 :(得分:0)
让我们看一下hashCode()
的实现和给定的提示:
public int hashCode() {
int hash = 0;
for (int i = 0; i < length(); i++)
hash = (hash * 31) + charAt(i);
return hash;
}
我们知道Aa
和BB
会产生相同的hash
,我们可以轻松地验证:
(65 * 31) + 97 = 2112
(66 * 31) + 66 = 2112
从这里开始,hash
对于两个输入都是相同的。也就是说,我们可以轻松地将任意数量的字符添加到两个字符串中,并且您将始终获得相同的值。
一个例子可能是:
hashCode("AaTest") = 1953079538
hashCode("BBTest") = 1953079538
因此,您可以通过更正式地将相同的字符序列附加到两个字符串来生成足够的哈希值:
hashCode("Aa" + x") = hashCode("BB" + x)
关于生成所有可能的字符串并搜索重复项的想法的另一条注释。看看bithday paradox,您会发现查找不同输入的重复哈希值将花费更少的时间。
答案 3 :(得分:0)
仔细研究一下散列函数,它的工作原理类似于数字系统(例如十六进制),其中数字的权重为31。也就是说,可以将其视为将数字转换为以31为底的数字,这样就可以进行最终的哈希运算了像hashCode = (31^n) * first-char + (31^n-1) * second-char + ..... + (31^0) * last-char
第二个观察结果是大写字母和小写字母之间的ASCII距离是32。用哈希函数解释,这意味着当您用小写字母替换大写字母时,意味着您还要再添加1个字母到较高的数字,1到当前的数字。例如:
BB = (31)(B) + (31^0)B
也等于(31)*(B - 1) + (31^0)*(31 + B)
请注意,我只是从较高的数字中取出一个单位并加到较低的数字上,而没有改变总值。最后一个等式等于(31)*(A) + (a) == Aa
因此,要生成给定哈希码的所有可能的String,请从初始String开始,并通过用小写字母替换一个小字符,同时从较高位置减少一个小字符来将字符从右向左移动(如果适用) )。您可以在O(1)中运行它
希望这会有所帮助。