剑指Offer56-Ⅰ.数组中数字出现次数

  • 题目:剑指Offer56-Ⅰ.数组中数字出现次数
    一个数组仅有两个出现一次的数字,其它数字均出现两次;
    数组长度 >= 2(意思是说一定存在这样的两个目标数);

  • 思路:
    1.排序+遍历:时间O(nlogn),最坏O(n ^ 2),空间O(logn),最坏O(n);

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int n = nums.size();
        vector<int> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n - 1; ++i) {//下面要出现i+1,因此i最多只能取到n-2
            if (nums[i] == nums[i + 1]) ++i;//如果相同,就把这两个相同的数跳过
            else res.push_back(nums[i]); //否则说明当前数出现了一次,然后接下来去判断下一个
        }
        if (res.size() == 1) res.push_back(nums[n - 1]);//走到这里,说明第二个目标数在最后一步被跳过了,例如1,1,2,3,3,10

        return res;                                                                                                                 
    }
};

2.w姨的二分法:空间O(nlog(maxVal - minVal)):基本接近O(nlogn)了,但对值二分的思路还是值得借鉴的,空间O(1)
在这里插入图片描述

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int ab = 0;
        int minVal = INT_MAX, maxVal = INT_MIN, zeroCount = 0;
        for (auto x : nums) {
             ab ^= x;//全部数异或的结果,也就是那两个目标数异或的结果
             minVal = min(minVal, x);//最小值
             maxVal = max(maxVal, x);//最大值
            if (x == 0) zeroCount++;
        }
        if (zeroCount == 1) return {ab, 0};//排除右一个0的情况,方便之后出现0,一定是两个相同数异或得到的

        int l = minVal, r = maxVal;//对值二分,而不是对下标二分
        while (l <= r) {
            int mid = l < 0 ? l + (l + r) / 2 : l + (r - l) / 2;
            int a = 0, b = 0;//a是所有左半边数的异或结果,b是右半边
            for (auto x : nums) {
                if (x <= mid) a ^= x;
                else b ^= x; 
            }

            if (a != 0 && b !=- 0) return {a, b};//说明两个目标数分别落在了<=mid和>mid两个区间内,这样两组的异或结果就分别是目标数
            else if (a == 0) l = mid + 1;//两个目标数都在右区间
            else r = mid - 1;//两个目标数都在左区间
        }

        return {};//如果数组一定存在这样的两个目标数的话, 就不会走到这个分支;
    }
};

3.(最优解)分组异或:时间O(n):遍历两次,空间O(1)
第一次遍历:将所有数异或得到ab,ab就是那两个目标数的亦或结果,由于这俩数不同,因此ab一定不为0。找到ab的二进制任意为1的位mask(之所以该位为1,就是因为这俩目标数的该位不同),为了方便直接找二进制为1的最低位;
第二次遍历:借助这点把数组分成两组,主要目的就是为了把这俩数分开,单由于其它数均出现两次,那么相同数的mask位一定相同,因此定会分在同一组。两组各自异或,各自得到一个目标数a,b;

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int ab = 0, a = 0, b = 0;
        for (auto x : nums) ab ^= x;//得到两个目标数异或的结果
        
        int mask = 1;
        while ((mask & ab) == 0) mask <<= 1;//循环寻找ab的二进制的最低位为1
		// int mask = (ab & (-ab));//寻找ab的二进制的最低位为1的最优解


        //按mask位是否为1分成两组,主要目的是把只出现一次的两个数字分开
        //出现两次的数字的mask位一定相同,因此一定能分到同一组
        //两组各自异或,最后各自得到只出现一次的数字
        for (auto x : nums) {
            if (x & mask) a ^= x;
            else b ^= x;
        }

        return {a, b};
    }
};

//y总写法
class Solution {
public:
    vector<int> findNumsAppearOnce(vector<int>& nums) {
        int sum = 0;
        for(auto x : nums) sum ^=x;//两目标数异或
        
        int k = 0;
        while(!(sum>>k & 1))k++;//按第k位分组
        
        int first = 0;
        for(auto x: nums)
            if(x>>k&1)
                first ^= x;//只需要算一组;
        return vector<int>{first, sum^first};//a ^ (a ^ b) = b;
    }

};
  • 总结:
两个相同数异或为0,与0异或不变;

x ^ (x-1):将x二进制为1的最低位去掉

x ^ (-x):得到x二进制为1的最低位

int mask = 1while ((x & (mask)) == 0) mask <<= 1; //得到x二进制为1的最低位

只要存在单调性,就能二分!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值