位运算

a = 60  # 0011 1100
b = 13  # 0000 1101
运算符 描述 示例
& 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 a& b: 0000 1100
| 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 a|b: 0011 1101
~ 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1 ~a: 1100 0011
^ 按位异或运算符:当两对应的二进位相异时,结果为1 a^b: 0011 0001
<< 左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0。 a<<2: 1111 0000
>> 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数 a>>2: 0000 1111

位运算在数据结构的编程题目中也常常出现,通常位运算的算法都是时间和空间复杂度比较占优的一种解法。接下来总结几道leetcode中涉及位运算的解法题目。

leetcode 268. 缺失数字

题目描述:

给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。 

示例 1: 
输入: [3,0,1]
输出: 2

示例 2: 
输入: [9,6,4,2,3,5,7,0,1]
输出: 8

说明: 
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现? 

大部分人首先想到的是利用散列表来对序列中每个数字进行计数,最后遍历0-n,计数为0的数即为所求。此解法时间复杂度为O(n),空间复杂度O(n)

那我们如何做到常数空间复杂度O(1)呢?
这里就要利用到我们的位运算了。如果一个数字出现了两次,则它们的异或^结果为0,我们可以利用这个性质,来对序列中的每个数字求一次异或,再对0-n中的数字求一次异或,则未缺失的数字都出现了两次,而缺失数字只出现了一次,出现两次的数字异或结果都为0,则最后留下的便是我们需要求得的缺失数字。

class Solution:
    def missingNumber(self, nums):
        """
        异或运算,与本身为0。
        :param nums:
        :return:
        """
        res = 0
        for i in range(len(nums)+1):
            res ^= i
        for num in nums:
            res ^= num
        return res
leetcode 260. 只出现一次的数字

题目描述:

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 
找出只出现一次的那两个元素。 

示例 : 
输入: [1,2,1,3,2,5]
输出: [3,5] 

注意: 
结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。 
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现? 

题目的难度有所上升了哈,如果我们还是对这个序列中的元素进行异或运算,那最后会得到什么呢?
假如只出现一次的两个元素是 xy,由于其余元素均出现了两次,则其余元素的异或结果最后为0,最后剩下的便是 xy 异或的结果了。我们知道异或运算保留的是两个数字二进制位的差异,有了这个差异我们又能做什么呢?

# 如示例中所述 [1,2,1,3,2,5]
>>> bin(3)
'0b11'
>>> bin(5)
'0b101'
>>> bin(3^5)
'0b110'

我们注意到这个差异中的 1 均来至于 xy,且只能来至于其中一个元素。那我们能否利用这些 1xy 分开呢?答案是肯定的。
分离 xy只需要提取到其中一个 1 便够了。我们定义这个差异为 mask,即 mask = 3^5,那 -mask 是什么呢?——取反再+1。我们通过 mask & (-mask) 来得到 mask 最右边的 1

>>> mask = 3^5
>>> bin(-mask)
'-0b110'
>>> bin(mask&(-mask))
'0b10'

利用这个 1xy 分离,便得到了最终结果。

class Solution:
    def singleNumber(self, nums):
        """
        位运算
        :param nums:
        :return:
        """
        # 保留出现一次的两个数字的差异
        mask = 0
        for num in nums:
            mask ^= num
        # 记录最右边的1,这个1一定来至于其中一个数字,且只来至于那一个数字。我们可以根据这个1来分离这两个数字
        diff = mask & (-mask)
        res = 0
        for num in nums:
            if diff & num:
                res ^= num
        return [res, mask^res]
leetcode 645. 错误的集合

题目描述:

集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个
元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有
一个元素重复。 

给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到
重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。 

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

注意:  
给定数组的长度范围是 [2, 10000]。 
给定的数组是无序的。 

数组 nums 总共有 n 个元素,其中有一个元素重复。我们对1…n 的数字进行异或运算,再对 nums 中的数字进行异或运算,那结果便只剩下重复元素和丢失元素的差异了,联想到 leetcode 260,再将这两个元素分离,便得到了结果。

class Solution:
    def findErrorNums(self, nums: List[int]) -> List[int]:
        mask, xor0  = 0, 0
        for idx, num in enumerate(nums, 1):
            mask ^= idx ^ num
        diff = mask & (-mask)
        for idx, num in enumerate(nums, 1):
            if diff & num:
                xor0 ^= num
            if diff & idx:
                xor0 ^= idx
        for num in nums:
            # 即为重复数字
            if num == xor0:
                return [xor0, mask ^ xor0]
        return [mask ^ xor0, xor0]

前面几道题都是讲的异或 ^ 的算法,接下来我们看看这道题该采用什么算法呢?

leetcode 287. 寻找重复数

题目描述:

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),
可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。 

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

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

说明: 
不能更改原数组(假设数组是只读的)。 
只能使用额外的 O(1) 的空间。 
时间复杂度小于 O(n2) 。 
数组中只有一个重复的数字,但它可能不止重复出现一次。 

我们知道1 的二进制表示为 0000 0001,若将 1 左移一位,即可得到 0000 0010,其中的数字 1 移动到了第2位;若将 1 左移两位,即可得到 0000 0100,其中的数字 1 移动到了第3位。数组 nums 中的数字都在 1n 之间,若将 1 依次移动 1…n 位,则可得第 2…n+1 位置的 1。为了将每一位的 1 都保留下来,我们可以采用 | 运算。若这个位置的 1 已经被占用了,则表示这个数字为重复数字。

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        flag = 0
        for num in nums:
            if flag & (1 << num) > 0:
                return num
            flag |= (1 << num)
        return -1
leetcode 169. 多数元素

题目描述:

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 
你可以假设数组是非空的,并且给定的数组总是存在多数元素。 

示例 1: 
输入: [3,2,3]
输出: 3 

示例 2: 
输入: [2,2,1,1,1,2,2]
输出: 2

这道题采用位运算的前提是数组中的元素都为整数。虽然题目没有明确要求,但是位运算算法也能通过测试,所以题目默认应该是整数数组。当然,负数也是有可能的。
C 或者 java 语言中,整型占用32字节,而 python 中整型为动态类型,且没有最大值限制。我们这里的解法都是基于整型占用32位的前提下的。

想象数组中每个数字都是由32个位置所组成,每个位置为 0 或者 1。出现次数大于 ⌊n/2⌋ 的这个元素,每个位置出现的 01 的次数也是大于 ⌊n/2⌋ 的。由于 0 只是占位,1 才能帮助还原数字,所以我们可以统计每个位置 1 出现的次数,如果出现次数大于⌊n/2⌋,则计算到还原数字中。

class Solution:
    def majorityElement(self, nums) -> int:
        """
        位运算 这里应该再加一个前提 数组中的元素为整数 且每个整数占用32字节
        :param nums:
        :return:
        """
        res = 0
        n = len(nums) >> 1
        for i in range(32):
            ones = 0
            # 统计每个位置1出现的次数
            for num in nums:
                # 为了处理出现负数
                ones += (num + 2 ** 31) >> i & 1
            # 如果这个位置1出现的次数满足条件,则是目标数字
            if ones > n:
                res += 1 << i
        return res - 2 ** 31
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352