【位运算】只出现一次的数字系列

136. 只出现一次的数字

在这里插入图片描述

  • 异或:两个bit运算时,相同为0,相异为1;两个数运算时,任何数和0异或不变

  • 交换律:a ^ b ^ c <=> a ^ c ^ b

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        // 异或:相同为0,相异为1,任何数和0异或不变,和1异或取反
        int ans = 0;
        for(int num : nums) ans ^= num;
        return ans;
    }
};

137. 只出现一次的数字 II

在这里插入图片描述

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int bit_freq[32] = {0};
        unsigned int bit = 1;
        for(int i = 31; i >= 0; i--){
            for(int num : nums){
                if(num & bit){
                    bit_freq[i]++;
                }
            }
            bit <<= 1;
        }
        int ans = 0;
        for(int i = 0; i < 32; i++){
            ans = (ans << 1);
            ans += bit_freq[i] % 3;
        }
        return ans;
    }
};

把所有的数都用二进制形式表示,用一个长度为32的数组存放每个位1出现的次数,如下:

在这里插入图片描述

把目前得到的数组1340 3014,每个元素都模3,余数就是原数组只出现一次数字的二进制表示(1010 0011)将此二进制还原即可

这种方法还可以解决某个元素只出现1次,其他元素出现n次的题型,对n取模即可

恢复二进制表示时,由于bit_freq[0]存放高位,而bit_freq[31]存放低位,所以从bit_freq[0]开始恢复,每次将ans左移后再加上bit_freq[i] % 3

260. 只出现一次的数字 III

在这里插入图片描述

由于异或运算满足交换律,且相同的数异或为0,任何数和0异或不变,所以整个数组逐元素异或的结果res就是两个只出现一次的a、b相异或的结果res

由于a、b两个数字不一样,所以异或的结果不为0,我们找到res的二进制表示中第一个为1的bit(这个bit表示a、b对应位置的bit异或结果位1,即a、b该位置的bit不同),以这个bit是否为1,将整个数组分为两部分,这样相同的数肯定分在同一数组中,而a、b肯定不在一个数组中

数组中所有元素都出现2次,求出只出现一次的数字,整个数组逐元素相异或即可

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int n = nums.size();
        if(n < 3) return nums;
        int res = 0;
        for(int num : nums) res ^= num;
        int bit = 1;
        while((res & bit) == 0){
            bit <<= 1;
        }
        int ans1 = 0;
        int ans2 = 0;
        for(int num : nums){
            if(num & bit) ans1 ^= num;
            else ans2 ^= num;
        }
        return {ans1, ans2};
    }
};

剑指 Offer II 070. 排序数组中只出现一次的数字

在这里插入图片描述

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int ans = 0;
        for(int num : nums){
            ans ^= num;
        }
        return ans;
    }
};

二分查找

正常情况下,如果没有那个只出现一次的数字,所有的数应该先出现在偶数下标,再出现在奇数下标,比如:

在这里插入图片描述

一旦出现了那个只出现一次的数字,后面所有的数字都会先在奇数下标出现,后出现在偶数下标,比如:
在这里插入图片描述

首先我们要知道,最终只出现一次的那个数字一定在偶数下标

我们使用二分查找,mid下标分为以下四种情况:
在这里插入图片描述

  • mid为奇数,和左边相同:答案在右边
  • mid为奇数,和右边相同:答案在左边
  • mid为偶数,和左边相同:答案在左边
  • mid为偶数,和右边相同:答案在右边
  • mid为偶数,和左右两边都不同,mid就是答案
class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        int l = 0;
        int r = nums.size() - 1;
        int ans;
        while (l <= r) {
            int mid = l + ((r - l) >> 1);
            if (mid & 1) {
                if (nums[mid] == nums[mid - 1]) l = mid + 1;
                else r = mid - 1;
            }
            else {
                if (mid > 0 && nums[mid] == nums[mid - 1]) r = mid - 1;
                else if (mid < nums.size() - 1 && nums[mid] == nums[mid + 1]) l = mid + 1;
                else {
                    ans = nums[mid];
                    break;
                }
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bugcoder-9905

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

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

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

打赏作者

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

抵扣说明:

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

余额充值