利用位运算查找元素

常用算法与数据结构模板系列(八)

Posted by John Mactavish on September 18, 2020

偶数个相同的数字按位异或为零。假如一个集合里有一个数字出现奇数次,其他数字出现偶数次; 那么对所有数字取异或操作,重复偶数次的数字都清零,留下来的就是那一个重复奇数次的数。

# 136. Single Number

# 找出数组中不重复的元素。其它元素出现两次。
# Input: [4,1,2,1,2]
# Output: 4

def single_num(nums):
    return reduce(lambda x, y: x ^ y, nums)

在此基础上还有:

260. Single Number III

找出数组中两个唯一出现一次的元素,其余元素均出现两次。原题
Input:  [1,2,1,3,2,5]
Output: [3,5]

如果我们可以把所有数字分成两组,使得:

  1. 两个只出现一次的数字在不同的组中;
  2. 相同的数字会被分到相同的组中。

那么对两个组分别进行异或操作,即可得到答案的两个数字。这是解决这个问题的关键。 具体的,我们做:

  1. 先对所有数字进行一次异或,得到两个出现一次的数字的异或值。
  2. 在异或结果中找到任意为 1 的二进制位。
  3. 根据这一位对所有的数字进行分组。
  4. 在每个组内进行异或操作,分别得到两个数字。

因为这两个数字只出现一次,所以它们的二进制表示中至少有一位不同从而在异或值中为 1,则可根据该位 区分开两个数,这就满足了条件 1; 因为两个相同的数字的对应位都是相同的,所以都会被分到同一组,满足了条件 2。

def singleNumber(self, nums):
    total_xor = self.get_xor(nums)
    mask = 1
    while total_xor & mask == 0:
        mask <<= 1
    p1 = [num for num in nums if num&mask==0]
    p2 = [num for num in nums if num&mask!=0]
    return [self.get_xor(p1), self.get_xor(p2)]
    
def get_xor(self, nums):
    from functools import reduce
    return reduce(lambda x, y: x ^ y, nums)

另外还有“数组中一个数字出现一次,其他数字出现奇数次”的问题:

剑指 Offer 56 - II. 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:
输入:nums = [3,4,3,3]
输出:4

示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
 
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31

因为三个相同的数字的异或结果还是该数字,我们这里不能应用异或运算, 但还是可以用按位统计的思路,即统计某个二进制位为 1 的数字个数。 如果一个数字出现三次,那么它的二进制表示的每一位(0 或者 1)也出现三次。 如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位的和都能被 3 整除。如果某一位的和能被 3 整除,那么那个只出现一次的数字二进制表示中对应的那一位是 0;否则就是 1。 例如:

二进制:
        110110
        110110
        110110
        101011
        101011
        101011
        001010
cnt:   
        634363

实现为:

def singleNumber(self, nums, n = 3):
    ans = 0
    for i in range(32):
        count = 0
        for num in nums:
            if ((num >> i) & 1):
                count += 1
        ans |= ((count % n != 0) << i)
    return self.convert(ans)

# convert 的作用是因为 python 中的 int 是个对象,且没有最大限制,不是在第 32 位使用 1 来表示负数。
def convert(self, x):
    if x >= 2**31:
        x -= 2**32
    return x

参考:

LeetCode-Solution

LeetCode算法题整理(位运算篇)Bit Manipulation from Xiaoliji’s Blog

mo-fei-25 的 LeetCode 解答

最后附上 GitHub:https://github.com/gonearewe