最近一直在面试,也做了各种各样的手写算法题,大部分时候面试官想要考察的只是候选人对常见算法的了解程度。有些题很难,通过一些骚操作可以达到更高的性能,比如最长回文子串
问题的最优解是马拉车算法
,但是那些算法太偏门了,需要深厚的理论基础,我们不是专门做算法的,可能面试官自己也都不会,他出这道题一般是想你用动态规划来解。(当然了,你就用马拉车算法来做肯定会让面试官眼前一亮,留下深刻的印象)还有一种情况就是题目很简单,简单的一两个加减乘除都能做出来,这时候面试官想考察的肯定不是你会不会做算术,这时候一般都是考察候选人位运算玩的溜不溜。
我这两天就遇到这样的问题,今天就主要来谈谈异或运算在面试中的考察方式。
首先来看一道题:在一个非空整数数组中,除了一个数其它数都出现了两次,找出这个数。
找出某个元素出现的次数,这个概念我们很熟悉呀,用哈希表就行了嘛!做一个循环,第一次遇到一个数的时候把它添加进哈希表,第二次遇到的时候移除,最后表里剩下来的那个就是我们要找的数。但是这样时间复杂度跟空间复杂度都在O(n),而且这么做太简单了,怎么办,还有没有其它解法?
我们回想一下异或运算符的特性,两个操作数相同的话为0,任何数与0做异或的结果还是那个数。
这样我们可以对数组里面的所有元素做异或操作,相同的两个数都会变成0,剩下的那个数跟0做异或结果还是那个数,最后我们就能得到我们的结果啦:
public static int findSingleNumber(int[] arr) {
int num = 0;
for (int i = 0; i < arr.length; i++) {
num = num ^ arr[i];
}
return num;
}
这时候时间复杂度还是O(n),但是空间复杂度已经达到O(1)喽。
现在我们把难度升级一下,数组里存在两个唯一的数,我们该怎么把它找出来呢?还是按照之前的方法的话,我们只能得到n1^n2
,拿不到独立的数的呀?难道还是哈希表大法好?
别急,我们知道n1跟n2是不同的数,那么在它们二进制的表示上,至少有一位是不一样的,那我们可以跟这一位上是1还是0把数组里的数分成两拨,这两个数肯定就在各自的分组里,然后相同的数也肯定在同一个分组里,再让这两拨数做异或运算,最后各自剩下来的不就是我们要的两个数了吗?
那么怎么找到这不一样的位置呢?还记得位运算的特性,0跟1做运算下来还是1
,我们从右向左依次跟1做与运算,就能找到它了。
public static int[] findSingleNumbers(int[] nums) {
// 得到做完异或操作之后的结果
int n1xn2 = 0;
for (int num : nums) {
n1xn2 ^= num;
}
// 得到从右往左第一个是1的位
int rightmostSetBit = 1;
while ((rightmostSetBit & n1xn2) == 0) {
rightmostSetBit = rightmostSetBit << 1;
}
int num1 = 0, num2 = 0;
for (int num : nums) {
if ((num & rightmostSetBit) != 0) // 一拨
num1 ^= num;
else // 另一拨
num2 ^= num;
}
return new int[]{num1, num2};
}
通过与运算,我们成功把数组分为两拨,然后再通过异或操作,得到我们的结果。
最后再来看看我遇到的这道题:每个非负整数 N 都有其二进制表示。例如, 5 可以被表示为二进制 "101",11 可以用二进制 "1011" 表示,依此类推。注意,除 N = 0 外,任何二进制表示中都不含前导零。二进制的反码表示是将每个 1 改为 0 且每个 0 变为 1。例如,二进制数 "101" 的二进制反码为 "010"。给你一个十进制数 N,请你返回其二进制表示的反码所对应的十进制整数。
我们再来回顾一下异或运算的特性:
- 10=01=1
- 00=11=0
- 任何数跟0异或都不变
从第一点我们可以知道一个数跟它的反码异或会得到一个各位都是1的数。我们就可以写出下面的公式:
number ^ complement = all_bits_set
那么:
number ^ number ^ complement = number ^ all_bits_set
然后:
0 ^ complement = number ^ all_bits_set
最终:
complement = number ^ all_bits_set
我们可以基于这条公式去求我们想要的结果了。现在的任务就变成如何获取这个all_bits_set
了,也很简单,知道这个数占几位就好了。
public static int bitwiseComplement(int num) {
// 计算位数
int bitCount = 0;
int n = num;
while (n > 0) {
bitCount++;
n = n >> 1;
}
int all_bits_set = (int) Math.pow(2, bitCount) - 1;
// 根据我们之前推导的公式
return num ^ all_bits_set;
}
时间复杂度根据位数决定,空间复杂度维持在O(1)。
总而言之,这类的题型其实很固定,一堆数里找特定的数啊,一个数的特定变形啊,我们只要关注异或运算那三种特性,那解题就没有太大障碍了。
关注我,一起讨论算法吧
网友评论