位运算初探——只出现一次的数字

232 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

介绍

位运算,一个高效的东西,同时也是一直令我非常困扰的一大奇妙算法。在这篇文章中我以一个很简单例子对位运算做一个简单的介绍与探究。

首先来看例子LC136,题目如下:

题目很简单,一下就能读完。读完这个题目第一时间就能想到一个简单的解法:使用一个哈希表存储每个数字出现次数,然后最后遍历一遍找只出现一次的数字就行了。

但是题目中的说明要求算法具有线性时间复杂度,并且不使用额外空间。看到这个要求,其实除了位运算好像还真没啥别的方法了。那么对于这道题位运算怎么做呢?

这时候就该去思考如何来使用数字的位信息。我们都知道,每一个数字都能转换成二进制数,转换完后每一位不是0就是1,那么我们如何来运用这个位的信息?针对这道题,你可以看到它有一个特殊的约束:除了某个元素只出现一次以外,其余每个元素均出现两次。这个两次就很关键啊,除了答案外相同的元素有且仅有两次,这不是和位运算中异或的归零律相匹配么?

异或:如果异或的两个数不相同,则异或结果为 1,如果异或的两个数相同,则结果为 0 异或的归零率 a \bigoplus a = 0

而且异或运算还存在交换律结合律,这也就是说我们把数组中全部的数都异或起来,相同的数只存在两个,那相同的数相碰不就为 0 了吗。同时由于** 0 异或任何数结果都是那个数**,那可见最终全部的数异或出的答案就是那个只出现一次的数字了。

0异或任何数结果都是那个数 出自 异或的恒等率:a \bigoplus 0 = a

为了理解得更加清楚,我把0,1两两异或的结果放在这里,你们可以将数转化为二进制再每一位异或一下看看结果,可以更加深刻的理解这个算法。

aba \bigoplus b
000
011
101
110

到这里,解法就很明确了,这就是位运算了!

class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for (int num : nums)
            ans ^= num;

        return ans;
    }
}

代码也是非常的简单,位运算十分简洁高效。

进阶

这个题目加的限制是相同的元素有且仅有两次,那如果不止两次呢?这道LC137可不就来了,它把两次改为了三次,同样要求算法具有线性时间复杂度,并且不使用额外空间,很明显也是要使用位运算的。

由于这篇文章只是初探,就讲到这里,我会在后面的文章对这道LC137做更进一步的探究!

结论

对于要求算法具有线性时间复杂度,并且不使用额外空间时,不用犹豫,这大概率就是要使用位运算才能解决。这个时候首先要看清有些什么约束条件,然后根据不同的位运算以及他们的一些性质来思考如何才能有效的利用这些性质。

对我来说位运算还是挺难的,一起加油攻破这道难关吧!