判定字符是否唯一的面试题想到
题目出处 力扣(LeetCode) https://leetcode-cn.com/problems/is-unique-lcci/
实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
示例 1:
输入: s = "leetcode"
输出: false
示例 2:
输入: s = "abc"
输出: true
限制:
0 <= len(s) <= 100
如果你不使用额外的数据结构,会很加分。
就是这么简单一道题,居然看到灰常多的解法。虽然题目提示尽量不用额外的数据机构,但实际开发中,内置的数据结构除了内存稍微多了那么一点点,我觉得并没什么不妥。但作为面试题来做就按出题者的意图来做吧。
java8 stream
执行效率2ms 内存36.4左右 一行代码就搞定
public static boolean isUnique(String astr) {
return astr.chars().distinct().count() == astr.length();
}
set去重一
执行效率0ms 内存36.4左右
public boolean isUnique(String astr) {
HashSet<Character> set = new HashSet<>();
for(char c : astr.toCharArray()){
if(!set.add(c)) {
return false;
}
}
return true;
}
set去重二
执行效率0ms 内存36.1左右
public boolean isUnique(String astr) {
HashSet<Character> hashSet = new HashSet<>();
for (int i = 0; i < astr.length(); i++) {
hashSet.add(astr.charAt(i));
}
return hashSet.size() == astr.length() ;
}
set去重三
执行效率0ms 内存36.1左右
public boolean isUnique(String astr) {
HashSet<Character> set = new HashSet<>();
for (int i = 0; i < astr.length(); i++) {
if(!set.add(astr.charAt(i))) {
return false;
}
}
return true;
}
set去重一、二大致相同,但第一种方法中astr.toCharArray()方法自然是多用了内存。而去重三和二比的话在某些情况有不同之处的
传统遍历一
执行效率0ms 内存36.2
## 当前字符是否在 是否包含在后续的字符中
public boolean isUnique(String astr) {
for(int i=0;i<astr.length();i++){
if(astr.substring(i+1).contains(String.valueOf( astr.charAt(i) )) )
return false;
}
return true;
}
传统遍历二
执行效率0ms 内存36.1
## 当前字符,从后往前找索引是否一致
public boolean isUnique3(String astr) {
for(int i=0;i<astr.length();i++){
if(astr.lastIndexOf(astr.charAt(i))!=i)
return false;
}
return true;
}
位运算
属于大神级表演
-
int 32位 long 64位
-
位运算
long bitIndex = 1L << c; 表示将二进制的0001(这里是64位,前面的零省略了。)向左边移动c个位置,那么就可以得到唯一对应的站位
low64 |= bitIndex; 表示和之前的那个拼接起来
low64 & bitIndex; 与操作,如果没出现过,结果是0,如果出现过,则不会是0 -
ASCII码
ASCII码是0-127 一共128个,可以代表所有的简单字符。
思路
由于ASCII码字符个数为128个,而且题目说了如果你不使用额外的数据结构,会很加分。因此可以使用两个64位的long变量来存储是否出现某个字符,二进制位1表示出现过, 0表示未出现过。具体代码如下
public boolean isUnique(String astr) {
long low64 = 0;
long high64 = 0;
for (char c : astr.toCharArray()) {
if (c >= 64) {
long bitIndex = 1L << (c - 64);
if ((high64 & bitIndex) != 0) {
return false;
}
high64 |= bitIndex;
} else {
long bitIndex = 1L << c;
if ((low64 & bitIndex) != 0) {
return false;
}
low64 |= bitIndex;
}
}
return true;
}
这里还看到如下一段阐述
如果我是面试官,我会希望看到什么?
可能有点偏题,毕竟这里主要就是做题的地方。。
如果我是面试官,会考虑主要考察什么,就我的工作经验看,大多数主要是招聘工程师的,面试者如果什么问题都没有,直接写个二重循环搞定,会首先给个50分,如果能写点判断字符串是否为null的,60分。
直接上手什么bitset,什么位运算的,我会先问他,题目中有没有交代字符串的字符一定是26个英文字母?如果是unicode环境,你是不是要准备2^16/8个字节的空间?在实际项目中,风险可控,结果可期更重要,绝大多数时候不在乎那点时间和资源。
所以我期望面试者不要急于解答,我希望他先问我问题:
字符串的字符范围,如果我告诉他,26个小写英文字母,那可能一开头直接判断如果字符长度>26, 直接返回False,做到这一点的,80分
如果我告诉他ascii字符集,然后他的代码里有边界检查,并且针对不同的范围有不同的侧重点,比如说ascii字符集,那也就是128个可能性,16个字节的位运算比较好
如果我告诉他是unicode,没有字符范围,老老实实排序再判断是比较符合我对工程师的要求的,因为算法性能稳定,没有额外资源要求,一眼看出没什么不可预见的风险,100分。
就是说,有些东西,没想到或者一时没想到根本不是问题,日常工作中稍微提示一下即可,但是缜密的思维对于程序员来说更重要。
一道看似平平无奇的面试题,解法多种多样。很多时候我们在工作中的时候,在大多数情况下只会写一种解决方案,甚至你也只会想一种解决方法,也不会去比较哪个更优秀。但如果真正的有这种思考的态度,或许做技术久了,差距就出来了