位运算的妙用:异或

认识异或

  1. 异或即无进位加法 0101 1011 ^ 1010 0101 = 1111 1110
  2. 满足交换律和结合律(阿贝尔群):a ^ b = b ^ a(a ^ b) ^ c = a ^ (b ^ c)
  3. 单位元 0a ^ 0 = a;逆元 aa ^ a = 0
  4. 根据 3. 可推广为:偶数个 a 异或则会 0;奇数个 a 异或则还是为 a

swap

交换俩数

vector<int> swapNumbers(vector<int>& a) {
    a[0] ^= a[1]; // a[0] = a[0] ^ a[1];
    
    a[1] ^= a[0]; // a[1] = a[1] ^ a[0] ^ a[1]
                  // 交换律:a[1] = a[1] ^ a[1] ^ a[0]
                  // 逆元:  a[1] = 0 ^ a[0]
    	          // 单位元:a[1] = a[0]
    a[0] ^= a[1]; // a[0] = a[0] ^ a[1] ^ a[0]
                  // 交换律:a[0] = a[0] ^ a[0] ^ a[1]
                  // 逆元:a[0] = 0 ^ a[1]
    			  // 单位元:a[0] = a[1]
    // 所以最终结果就是 a[0] = a[1], a[1] = a[0]
    return a;
}

只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

int singleNumber(vector<int>& nums) {
    int res = 0;
    for (auto x : nums) res ^= x;
    return res;
}

使用交换律将相同的两个数凑在一起,相同数互为逆元,全部变 00 又是单位元,所以就剩下只出现一次的数。

所以可推广为:

给定一个非空整数数组,除了某个元素只出现奇数次以外,其余每个元素均出现偶数次。找出那个只出现了奇数次的元素。

只出现一次的数字 III

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

// leetcode 有毒,卡边界
vector<int> singleNumber(vector<int>& nums) {
    long eor = 0;
    for (auto x : nums) eor ^= x; // 最后结果为 eor = one ^ two
    long rightOne = eor & (~eor + 1); // 取出最右边的1,这个 1 的位置
    								  // 对应于两个数位置上一是不同
    long one = 0;
    for (auto x : nums) {
        if ((x & rightOne) != 0) { // 将两个奇数次数分开
            one ^= x; // 得出第一个奇数次数
        }
    }
    long two = eor ^ one; // 得出第二个奇数次数
    return {(int)one, (int)two};
}
a:   1100 0011
b:   1100 1100
a^b: 0000 0011
            ^ 这个位置上,a 和 b 不一样
然后将两个数分开,根据性质就可以获取结果

所以可推广为:

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

错误的集合

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

无中生有

拿到一道新的问题,先在大脑中回想以前有没有做过的类似题目

看问题描述,返回答案是两个数:{重复的数,没有的数}

找规律:一个数字重复,那就是出现两次。没有出现的数,那就是出现零次

共同特点,两个数都是出现偶数次。如果我们找到这两个数,然后在数组中遍历一次就能分辨这两个数

所以,我们现在可以把问题转化为,在 数组中寻找两个出现偶次数的数

问题转化

在大脑中快速回想!有没有做过 寻找数组中只有两个出现偶次数的数 类似的题

果不其然!做过!寻找数组中只有两个数出现奇数次的数

这两个问题建立关系,将其前者转换为后者

回到题目还有一个条件没用:s 中的数是包含 1~n ,那么如果我把原来的数组后面再扩充 1~n

这时,原来是奇数次就变偶数次,原来是偶数次就变成了奇数次,不就转换为老问题了?

输入:nums = [1, 2, 2, 4]                1~n 中出现偶数次:2 3
扩充:nums = [1, 2, 2, 4, 1, 2, 3, 4]    1~n 中出现奇数次:2 3

数组实际不需要扩充,只需要多循环异或一次就好了

新瓶装旧酒

vector<int> findErrorNums(vector<int>& a) {
    // eor = 缺失的数 ^ 重复的数
    int eor = 0;
    for (auto x : a) eor ^= x;
    for (int i = 1; i <= a.size(); i++) eor ^= i;
    
    // 分离
    int rightOne = eor & (~eor + 1); 
    int one = 0;
    for (auto x : a) {
        if ((x & rightOne) != 0) one ^= x;
    }
    for (int i = 1; i <= a.size(); i++) {
        if ((i & rightOne) != 0) one ^= i;
    }
    int two = eor ^ one;

    // 分辨
    for (auto x: a) {
        if (x == two) swap(one, two);
    }
    return {one, two};
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值