我正在读《破解编程面试》这本书,我遇到了一些需要答案的问题,但我需要帮助来比较我的答案和解决方案。我的算法工作正常,但我很难理解书中的解决方案。主要是因为我不明白一些运算符真正在做什么。
任务是:"实现一个算法来确定字符串是否都是唯一的字符。如果不能使用其他数据结构该怎么办?"
这是我的解决方案:
public static boolean checkForUnique(String str){
boolean containsUnique = false;
for(char c : str.toCharArray()){
if(str.indexOf(c) == str.lastIndexOf(c)){
containsUnique = true;
} else {
containsUnique = false;
}
}
return containsUnique;
}
这是可行的,但效率如何呢?我看到Java中String的索引函数的复杂度是O(n * m)
以下是书中的解决方案:
public static boolean isUniqueChars(String str) {
if (str.length() > 256) {
return false;
}
int checker = 0;
for (int i = 0; i < str.length(); i++) {
int val = str.charAt(i) - 'a';
if ((checker & (1 << val)) > 0) return false;
checker |= (1 << val);
}
return true;
}
有几件事我不太明白的解决方案。首先,什么是"| ="运算符做什么?为什么从字符串中的当前字符减去" a "作为" val "的值?我知道"〈〈"是按位左移,但(checker & (1<<val))
做什么?我知道它是按位与,但我不理解它,因为我不理解checker获取值的行。
我只是不熟悉这些运算不幸的是书中没有给出解决方案的解释,可能是因为它假设你已经理解了这些运算。
8条答案
按热度按时间mmvthczy1#
这里有两个独立的问题:您的解决方案的效率如何,参考解决方案的效果如何?让我们分别讨论这两个问题。
首先,您的解决方案:
您的解决方案基本上由字符串中所有字符的循环组成(假设有n个),在每次迭代时检查字符的第一个和最后一个索引是否相同。
indexOf
和lastIndexOf
方法都花费时间O(n),因为它们必须扫描字符串中的所有字符,以确定是否有任何字符与您要查找的字符匹配。因为循环运行了O(n)次,每次迭代做O(n)个工作,所以它的运行时间是O(n2)。但是,您的代码有一些不确定之处。请尝试在字符串
aab
上运行它。它在此输入上是否正常工作?作为提示,一旦您确定存在两个或更多重复字符,您就可以保证存在重复字符,并且可以返回并非所有字符都是唯一的。现在,我们来看一下参考文献:
这个解决方案很可爱,基本思想如下:假设你有一个包含26个布尔值的数组,每个布尔值都跟踪字符串中是否已经出现了一个特定的字符,你从所有的布尔值false开始,然后遍历字符串中的字符,每次你看到一个字符,你就在数组槽中查找这个字符,如果它是
false
,这是你第一次看到这个角色,你可以把插槽设置为true
。如果是true
,你已经看到这个角色了,你可以立即报告有重复。注意,这个方法没有分配一个布尔数组,而是选择了一个聪明的技巧。由于
int
中只有26个不同的字符,而int
中有32位,因此该解决方案创建了一个int
变量,其中变量的每一位对应于字符串中的一个字符。该解决方案读取和写入该数的位。例如,看下面这行:
checker & (1 << val)
是做什么的?1 << val
创建一个int
值,除了val
位之外,所有位都为零。然后它使用按位AND将这个值与checker
进行AND运算。如果checker
中位置val
的位已经置位,那么它的值为非零值(意味着我们已经看到了这个数字),我们可以返回false,否则,它的值为0,我们还没有看到这个数字。下一行是这样的:
它使用“带赋值的位OR”运算符,该运算符等效于
此函数将
checker
与仅在位置val
处设置了1位的值进行OR运算,从而打开该位。这相当于将数字的第val
位设置为1。这种方法比你的方法要快得多。首先,由于函数一开始就检查字符串的长度是否大于26(我假设256是一个拼写错误),所以这个函数从来不需要测试任何长度为27或更长的字符串,因此,内部循环最多运行26次。(1)按位操作,所以完成的总工作量是O(1)(O(1)次迭代乘以每次迭代O(1)次工作量),这比您的实现要快得多。
如果您还没有见过以这种方式使用的位运算,我建议您在Google上搜索“位运算符”以了解更多信息。
t30tvxxf2#
我不喜欢书中的解决方案,而且我认为它是功能失调的...... templatypedef已经发布了一个全面的答案,表明这个解决方案是好的,我不同意,因为书中的答案假设字符串只有小写字符(ascii),并且没有验证来确保这一点。
底线是,考虑到templatedef的答案,实际上没有足够的信息来确定这本书的答案是否正确。
但我不相信。
templatedef对复杂性的回答是我同意的。-)
编辑:作为练习,我把书中的答案转换成了一个可行的答案(尽管比书中的答案慢--BigInteger慢)......这个版本做的逻辑和书中的一样,但没有同样的验证和假设问题(只是慢了一些)。
ylamdve63#
由于
char
值只能保存256个不同值中的一个,因此任何长度超过256个字符的字符串 * 必须 * 至少包含一个重复值。代码的其余部分使用
checker
作为位序列,每个位代表一个字符。它似乎将每个字符转换为整数,从a
= 1开始。然后检查checker
中的相应位。如果设置了该位,则意味着该字符已经被看到。因此我们知道字符串至少包含一个重复字符,如果还没有看到这个字符,代码就会设置checker
中的相应位并继续。具体而言,
(1<<val)
生成位置val
处具有单个1
位的整数。例如,(1<<3)
将为二进制1000
或8。如果未设置位置val
中的位,则表达式checker & (1<<val)
将返回零(即值为0)和(1<<val)
(如果设置了该位,则始终为非零)。表达式checker |= (1<<val)
将设置checker
中的该位。然而,该算法似乎存在缺陷:它似乎没有考虑到大写字符和标点符号(按字典顺序,它们通常在小写字符之前),而且似乎需要一个256位整数,这不是标准的。
正如rolfl在下面的评论中提到的,我更喜欢您的解决方案,因为它很有效,您可以通过在识别出非唯一字符后立即返回
false
来优化它。v9tzhpje4#
第6版更新
k97glaaz5#
书中的解决方案不区分大小写。根据实现,"a"和"a"被视为重复。
说明:对于具有字符"A"的输入字符串,"A"-"a"为-32,因此"1〈〈val"的计算结果为1〈〈-32。对任何负数进行移位都将以相反方向移位位。因此,1〈〈-32将为1〉〉32。这将把第一位设置为1。字符"a"也是这种情况。因此,"A"和"a"被认为是重复字符。类似地,'B'和'b'的第二位被设置为1,依此类推。
qxsslcnc6#
如“破解编码访谈”所述,存在替代解决方案:
当然,要实现更好的空间复杂度,请参考上面的示例**@ templatypedef**
hk8txs487#
如果有人需要它作为Javascript实现。@ whoopdoo和其他人已经清楚地说明了这一点。https://stackoverflow.com/a/46105690/8090964
vjhs03f78#
以下是对该书代码的必要更正: