今天主要想分享的是自己在面试过程中遇见的一道面试题,是一道简单的算法题。
在面试的过程中,我使用了 hash 表来解决的(时间复杂度和空间复杂度都是O(n)),但是面试官不满意,当时也实在没想到别的解法。
后来在慢慢的使用位运算的过程中,发现通过位运算,可以让时间复杂度为O(n),空间复杂度为O(1)的解法。那么先看下题目吧。
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。【leetcode 136. 只出现一次的数字】
例子:输入: [4,1,2,1,2] 输出: 4
看见这道题的第一个想到的就是 hash 表,看下面代码,思路很简单
class Solution {
public int singleNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (Integer i : nums) {
Integer count = map.get(i);
count = count == null ? 1 : ++count;
map.put(i, count);
}
for (Integer i : map.keySet()) {
Integer count = map.get(i);
if (count == 1) {
return i;
}
}
return -1; // 未找到
}
}
这个思路就很简单,不过时间复杂度和空间复杂度都是O(n)。
其思想:
思路很清晰很简单,但是面试官不满意呀。
这道题使用的就是异或^
这个位运算符。那么^
的原理是什么了?
异或^
:它可以对两个数的比特位进行比较。然后返回一些新的数。当2个操作数的对应位不相同时,该数的对应位就为1。
不好理解哈,看下图。
比如20(二进制表示为:00010100)和17(二进制表示为:00010001)进行异或,得到的值为5。将2个数的二进制进行比较,相同则为0,不相同则为1。
那这道题用异或的解法怎么写了?代码如下:
public int singleNumber(int[] nums) {
int lostNum = 0;
for (int num : nums) {
lostNum = lostNum ^ num;
}
return lostNum;
}
这样的解法的时间复杂度为O(n),空间复杂度为O(1)。确实很强。
关于为什么异或这样写可以解决这道题,我将官网的证明 copy 过来了,如下:
首先: 异或有一下3个性质: 任何数和 0 做异或运算,结果仍然是原来的数,即
a^0 = 0
。 任何数和其自身做异或运算,结果是 0,即a^a = 0
。 异或运算满足交换律和结合律,即a^b^a = a^a^b = 0^b = 0
为什么使用异或可以解这道题,证明如下 :
有些知识平常使用起来不是很多,但是有的时候这些不常用的知识却有着更高的解题效率。
因为我们写的代码最后都会转换成二进制在底层进行运行,所以位运算还是很有用的,比如求一个数的一半,我们平常会这么些num = num/2
,学了位运算后,可以使用右移运算符num = num >> 1
。右移一位等价于除以2。
好了,今天就分享到这里啦。