LeetCode之团灭异或问题

目录

题目

 一、不用额外变量交换两个数个数

二、136. 只出现一次的数字 

 三、int类型的数提取出最右侧的1来

四、137. 只出现一次的数字 II

五、260. 只出现一次的数字 III 

六、输出二进制1的个数

七、力扣89格雷编码 

 八、371. 两整数之和

参考文章


题目

 一、不用额外变量交换两个数个数

题目一链接

def swapNumbers(self, numbers: List[int]) -> List[int]:
    numbers[0] ^= numbers[1]
    numbers[1] ^= numbers[0]
    numbers[0] ^= numbers[1]
  return numbers
  • 异或(XOR)操作是可交换的,这意味着操作的顺序可以任意调整而不会影响最终的结果。换句话说,对于任何整数 a, b, 和 c,以下等式始终成立:a⊕b⊕c=a⊕c⊕b=b⊕a⊕c=b⊕c⊕a=c⊕a⊕b=c⊕b⊕a 

  • 任何数和0异或都等于本身
  • 相同的数异或等于0

二、136. 只出现一次的数字 

题目二链接 

一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

class xor():
    # 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数
    def xor(self, arr):
        n = len(arr)
        eor = 0
        for i in range(n):
            eor ^= arr[i]
        return eor

 三、int类型的数提取出最右侧的1来

假设n
则结果为 n&((~n)+1)

四、137. 只出现一次的数字 II

题目四链接 

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法且不使用额外空间来解决此问题。

建立一个长度为 32 的数组 counts,通过以上方法可记录所有数字的各二进制位的 1出现次数。

int[] counts = new int[32];
for(int i = 0; i < nums.length; i++) {
    for(int j = 0; j < 32; j++) {
        //这行代码执行位运算。具体来说,nums[i] & 1 这个表达式用来检查 nums[i] 的最低位是否为1。如果是,那么 nums[i] & 1 的结果就是1,否则就是0。
        counts[j] += nums[i] & 1;  
        //这行代码使用无符号右移操作符 >>>。它将 nums[i] 中的位向右移动一位,即丢弃最低位,并在最高位插入0。
        nums[i] >>>= 1;
    }
}

将 counts各元素对 3求余,则结果为 “只出现一次的数字” 的各二进制位。

for(int i = 0; i < 32; i++) {
    counts[i] %= 3; // 得到 只出现一次的数字 的第 (31 - i) 位 
}

利用 左移操作 和 或运算 ,可将 counts数组中各二进位的值恢复到数字 res 上(循环区间是 i∈[0,31])。

for(int i = 0; i < counts.length; i++) {
    res <<= 1; // 左移 1 位
    res |= counts[31 - i] % m; // 恢复第 i 位的值到 res(由于数据的特殊性counts[31 - i] % m)的结果只能是0或1
}

代码实现 

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        counts = [0] * 32  # 初始化一个长度为32的列表,用于统计位出现的次数

        # 统计每一位在所有数字中出现的次数
        for num in nums:
            for j in range(32):
                counts[j] += num & 1  # 如果当前位是1,就增加这一位的计数器
                num >>= 1  # 将数字右移一位,准备检查下一位

        res, m = 0, 3  # 初始化结果和模数值(因为除了一个数字外,其它数字都出现了三次)

        # 从位计数中重建那个只出现一次的数字
        for i in range(32):
            res <<= 1  # 将结果左移一位,为下一个位的结果腾出空间
            res |= counts[31 - i] % m  # 根据该位出现次数对模数3取余的结果(1或0),将该位加到结果中

        # ~(res ^ 0xffffffff)是处理res结果为负数的的情况
        return res if counts[31] % m == 0 else ~(res ^ 0xffffffff)
        # 如果最高位不是出现三次,说明只出现一次的数字是个负数。
        # 求得结果的二进制补码~(res ^ 0xffffffff),以获取负数。

五、260. 只出现一次的数字 III 

题目五链接 

给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。

解题思路

假设数组中这两种数为a和b,那么我对数组全部进行异或运算,得到的eor=a ^ b,一定如此。又因为a和b是两种数,那么eor=(a ^ b) != 0。那么eor一定存在某一位上不是0而是1。假设eor的第八位为1,那么我们可以将整个数组分为两个部分,即第八位上=0和第八位上=1的,并且a和b 一定是分属于两个部分的。此时,我想只异或第八位上=1的那部分,用eor’表示,那么eor’一定是a或者b,具体是谁不知道,假如是a那么如何去求b呢?那就是b= eor^eor’ (b= a ^ b ^  a)。这里第八位为1只是一个例子,那么找哪个呢?就找最右侧为1,根据第三题来求,知道了最右侧的1,就可以根据推理过程求出问题。

def singleNumber(self, arr: List[int]) -> List[int]:
        n = len(arr)
        eor = 0
        for i in range(n):
            eor ^= arr[i]
        # eor = a ^ b
        # eor != 0
        # eor 必然有一个位置上是1
        rightone = eor & (~eor + 1) # 提取最右侧为1
        onlyone = 0
        for i in range(n):
            if rightone & arr[i] != 0:
                onlyone ^= arr[i]
        otherone = onlyone ^ eor
        return onlyone, otherone
  • ~eor + 1~eor 的补码,这在二进制中相当于取反加一,也就是从 eor 的最低位开始的第一个1及其右边的所有位取反,而左边的所有位保持不变(因为在加一的过程中,这些位会逐个进位变为0,直到最低位的1停止进位)。
  • eor & (~eor + 1)eor 和它的补码进行按位与操作。由于补码中只有最右边的1保持不变,其他位都是0,所以这个按位与操作的结果就是保留了 eor 中最右边的那个1,而其他位都被清零了。
  • 在计算机系统中,数值一律用补码来表示和存储。补码的优势: 加法、减法可以统一处理,从而使得 CPU 只需加法器。这意味着以上方法 同时适用于正数和负数的加法 。

    比如eor =01010111000   ~eor + 1=10101001000

六、输出二进制1的个数

# 1.暴力法,遍历一个一个的数,for
# 2.用异或
class xor():
    # 输出二进制1的个数
    def xor(self, n):
        count = 0
        while n != 0:
            rightone = n & ((~n)+1)  # 每次先提取最右侧的1,rightone 只有一个数位为,其余为0
            count += 1  # 提取一个计数器加一个
            n ^= rightone  # 提取完就将最右侧的1抹掉
        return count

七、力扣89格雷编码 

题目七链接

n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:

  • 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1
  • 第一个整数是 0
  • 一个整数在序列中出现 不超过一次
  • 每对 相邻 整数的二进制表示 恰好一位不同 ,且
  • 第一个 和 最后一个 整数的二进制表示 恰好一位不同

给你一个整数 n ,返回任一有效的 n 位格雷码序列 。

第i为的格雷码为 i/2 ^ i

class Solution {
  
     public List<Integer> grayCode(int n) {
        List<Integer> result = new ArrayList<>();
        for (int i = 0; i < (1 << n); i++) {
            result.add(i ^ (i >> 1));
        }
        return result;
    }

}

 八、371. 两整数之和

题目八链接

public int getSum(int a, int b) {
    while (b != 0) {  // 当没有进位时停止循环
        int carry = (a & b) << 1;  // 计算 a 和 b 的进位,并左移一位,对应加法中的进位
        a = a ^ b;  // 使用异或运算符计算 a 和 b 的无进位和
        b = carry;  // 将进位值赋给 b,下次循环将其加到 a 上
    }
    return a;  // 当 b 为 0 时,a 就是最终的和
}
  1. 无进位和:

    • 异或运算 (a ^ b) 用于计算无进位和。在每个位上,异或操作模拟了加法:如果两个相应的位不同(即一个为 0,另一个为 1),结果为 1(无进位),如果两个位相同,结果为 0。
  2. 进位:

    • 与运算 (a & b) 计算进位。只有当两个位都是 1 时,才会产生进位。但进位影响的是下一位,因此需要将结果左移一位 (<< 1)。
  3. 重复过程:

    • 将步骤 1 的结果(无进位和)和步骤 2 的结果(进位)进行异或运算。这相当于把新的无进位和与新的进位相加。
    • 每次执行这个过程,进位都会被逐渐 "推" 到更高的位上,直到没有进位产生(即 b 变为 0)。
  4. 循环:

    • 这个循环继续进行,每次处理新的无进位和和新的进位,直到进位为 0。当没有进位时,意味着当前的无进位和就是最终的总和。

class Solution:
    def getSum(self, a: int, b: int) -> int:
        x = 0xffffffff
        a, b = a & x, b & x
        # 循环,当进位为 0 时跳出
        while b != 0:
            # a, b = 非进位和, 进位
            a, b = (a ^ b), (a & b) << 1 & x
        return a if a <= 0x7fffffff else ~(a ^ x)
  •  Python 没有 int , long 等不同长度变量,即在编程时无位数的概念。
  • 获取负数的补码: 需要将数字与十六进制数 0xffffffff。可理解为舍去此数字 32 位以上的数字(将 32 位以上都变为 0 ),从无限长度变为一个 32 位整数。 
  • 返回前数字还原: 若补码 a 为负数( 0x7fffffff是最大的正数的补码 ),需执行 ~(a ^ x) 操作,将补码还原至 Python 的存储格式。 a ^ x 运算将 1 至 32 位按位取反; ~ 运算是将整个数字取反;因此, ~(a ^ x) 是将 32 位以上的位取反,1 至 32 位不变。

参考文章

异或问题总结_zeronose的博客-CSDN博客

LeetCode2588之统计美丽子数组数目(相关话题:前缀异或和)_击水三千里的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值