剑指offer【位运算】

位运算

& 与: 两个位置都为1时才返回1(1&1=1, 1&0 =0)
| 或: 一个位置位1即可返回1 (1|0=1)
^ 异或: 两个位置,相同位0,不同为1 (1^1=0, 0^0=0, 1^0=1)
~ 取反: 1变0,0变1
<< 左移: 各二进制向左移动若干位,高位丢弃,低位补0
>> 右移: 各二进位全部右移若干位,对无符号数,高位补0

复合赋值,如 a &= b 即为 a = a&b

二进制中1的个数

逐位判断
  • 定义result = 0,用于记录1的个数
  • 使用 num & 1来判断最后一位是否为1,然后右移,遍历全部的位数
class Solution:
    def hammingWeight(self, n: int) -> int:
        res = 0
        while n:
            res += n & 1
            n >>= 1
        return res

求两数之和,不用运算符

使用位运算实现加法
  • 对于 a,b 两个数,第i位计算有以下几种形式
    1 + 1 = 0, 进位1
    1 + 0 = 1, 进位0
    0 + 1 = 1, 进位0
    0 + 0 = 0, 进位0
  • 使用c作为当前位置的计算结果,d作为进位计算结果
  • c的计算逻辑为 a ^ b, d的计算逻辑为a&b << 1 (进位1)
  • stop case d计算完了
通过abx来实现
  • 非进位和c直接通过a来存储,也就是 a^= b
  • 进位和直接存储在b上,也就是先计算x, 再 b=x
  • 最终直接return a即可
python负数的存储
  • Python 没有 int , long 等不同长度变量,即在编程时无变量位数的概念。
  • 取负数的补码: 需要将数字与十六进制数 0xffffffff 相与。可理解为舍去此数字 32 位以上的数字(将 32 位以上都变为 000 ),从无限长度变为一个 32 位整数。
  • 返回前数字还原: 若补码 aaa 为负数( 0x7fffffff 是最大的正数的补码 ),需执行 ~(a ^ x) 操作,将补码还原至 Python 的存储格式。 a ^ x 运算将 1 至 32 位按位取反; ~ 运算是将整个数字取反;因此, ~(a ^ x) 是将 32 位以上的位取反,1 至 32 位不变。
class Solution:
    def add(self, a: int, b: int) -> int:
        temp = 0xffffffff
        a, b = a & temp, b & temp
        while b != 0:
            # x = (a & b <<1) & temp # 进位和
            # a = a^b # 非进位和,直接赋值给a
            # b = x
            a, b = (a ^ b), (a & b) << 1 & temp

        if a <= 0x7fffffff: # 最大正数补码,也就是a是正数,直接输出
            return a
        else:
            return ~(a ^ temp)

数组中数字出现的次数(都出现两次,两个出现一次的数字)

如果只有一个落单的,可以直接用异或 ^ 去做

list = [a,a,b,b,....,x]

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        x = 0
        for num in nums:  # 1. 遍历 nums 执行异或运算
            x ^= num      
        return x;         # 2. 返回出现一次的数字 x
两个落单的,x,y
  • 因为相同的数字异或为0,任何数字与0异或结果是其本身。

  • 所以遍历异或整个数组最后得到的结果就是两个只出现一次的数字异或的结果:即 z = x ^ y

  • 根据异或的性质可以知道:z中至少有一位是1 (则x与y就是相等的)

  • 辅助变量m来保存z中哪一位为1.(可能有多个位都为1,我们找到最低位的1即可) e.g. z = 10 ^ 2 = 1010 ^ 0010 = 1000,第四位为1

  • 将m初始化为1,如果(z & m)的结果等于0说明z的末位为0;每次m左移,直到找到第一个z & m=1的位置,找到了首位1;

  • 首位1代表着,x,y这两个数与m做&会得到两个结果;利用这个特性把nums分成两批;再用上述的方法分别求出x,y

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        x, y, n, m = 0, 0, 0, 1
        #  遍历,求出n 用于存储 x^y的值
        for num in nums:      
            n ^= num
        # 循环左移,求出m(第一个1的位置)
        while n & m == 0:        
            m <<= 1       
        # 利用m进行分组
        # 如果和m同组,异或存到x里;
        # 如果另一组,异或存到y里
        for num in nums:        
            if num & m: x ^= num 
            else: y ^= num       
        return x, y              

数组中数字出现的次数(都出现三次,一个出现一次的数字)

数学法
  • 很简单的鸡兔同笼 假设想法
class Solution:
   def singleNumber(self, nums: List[int]) -> int:
       return (sum(set(nums))*3 - sum(nums))//2
遍历统计

对于出现三次的数字,各 二进制位 出现的次数都是 3 的倍数。
因此,统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字

  • 题目给出是32位,通过左移动依次生成 0001,0010,0100...
  • 先通过一个循环来生成每一个bit的flag
  • 对于当前这一位,遍历nums,数一下1的个数
  • 如果不是3的倍数,那这个1就是落单的那个数的,用或把它加到res里
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        res = 0
        for i in range(0,32):
            bit = 1 << i  # 第i位是1,实现 ..00010.. 的向左递归
            cur_count = 0 # 用于记录当前位置的1个数
            for num in nums:
                if num & bit:
                    cur_count += 1
            # 查完了所有的数字,看一下当前位是否为3的倍数,如果不是,这个数就是res
            if cur_count % 3 != 0:
                res |= bit
        return res

你可能感兴趣的:(剑指offer【位运算】)