【剑指Offer】数组中只出现一次的两个数字(其余数都出现两次)(普通排序 / 满足交换律的异或)

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

解法

排序然后左右比较

  1. 先将数组升序排序。
  2. 从第一个数开始,比较相邻两个数字xi,xi+1:如果相同,则往后移两位,继续比较;如果不同,则判断xi和前面数字是否也不同,xi+1和后面数字是否也不同,这样判断它数值的唯一性。
  3. 需要注意最后一个数字没参与比较,所以如果结果还差一个数,那就是差的最后这个数字。
  • 时间复杂度O(NlogN):N是数组大小,因为vector容器的排序是快排,最好、正常和平均时间复杂度都为O(nlogn),后面查找是O(N)
  • 空间复杂度O(1)
    【牛客】
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        sort(data.begin(), data.end());
        int size = data.size();
        bool findflag1 = false, findflag2 = false;
        
        int i = 0;
        while( i<=size-2 && (!findflag1 || !findflag2) ){
            if( data[i] == data[i+1] ){
                i += 2;
                continue;
            }
            int j = i;
            //data[i]和前面的数组比较,如果不相同,说明data[i]不等于后面也不等于前面,是个特数
            if( i==0 || (i>=1 && data[i-1]!=data[i]) ){
                if( !findflag1 ){
                    *num1 = data[i];
                    findflag1 = true;
                    j++;
                }else if( !findflag2 ) {
                    *num2 = data[i];
                    findflag2 = true;
                    j++;
                }
            }
            //data[i+1]和后面的数组比较,如果不相同,说明data[i+1]不等于前面也不等于后面,是个特数
            if( i==(size-2) || (i <= size-3 && data[i+1] != data[i+2]) ){
                if( !findflag1 ){
                    *num1 = data[i+1];
                    findflag1 = true;
                    j++;
                }else if( !findflag2 ) {
                    *num2 = data[i+1];
                    findflag2 = true;
                    j++;
                }
            }
            i = j;
        }
        //最后一个数
        if( !findflag1 ){
            *num1 = data[size-1];
            findflag1 = true;
        }
        if( !findflag2 ){
            *num2 = data[size-1];
            findflag2 = true;
        }
    }
};

【leetcode】

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        vector<int> anws;
        sort(nums.begin(), nums.end());
        int size = nums.size();
        int i = 0;
        while( i<=size-2 && ( anws.size()<2 ) ){
            if( nums[i] == nums[i+1] ){
                i += 2;
                continue;
            }
            int j = i;
            //nums[i]和前面的数组比较,如果不相同,说明nums[i]不等于后面也不等于前面,是个特数
            if( i==0 || (i>=1 && nums[i-1]!=nums[i]) ){
                anws.push_back(nums[i]);
                j++;
            }
            //nums[i+1]和后面的数组比较,如果不相同,说明nums[i+1]不等于前面也不等于后面,是个特数
            if( i==(size-2) || (i <= size-3 && nums[i+1] != nums[i+2]) ){
                anws.push_back(nums[i+1]);
                j++;
            }
            i = j;
        }
        //最后一个数
        if( anws.size()<2 ){
            anws.push_back(nums[size-1]);
        }
        return anws;
    }
};

满足交换律的异或

参考了leetcode讨论区某个高赞回答

  1. 对所有数字进行异或操作:因为异或满足交换率,相同的数都异或为0抵消了,剩下两个独数,所以异或结果其实是两个独数的异或结果。异或结果肯定有某一位为1,不然都是0的话就是相同数(两个独数一个在此位为0,另一个为1)。
  2. 找到这个位(随便哪一位都可以)并设为mask:从最低位0001开始,如果和异或结果的相与结果为0,那就不断左移,往更高位0010找
  3. 按此位将所有数分成两组:用mask来对所有数相与,根据与结果是否为0来将数分为两组
  4. 分成两组后,组内所有数各自异或:相同的两个数异或肯定为0(而且分开的时候,相同的两个数必为一组),最后剩下的每组里就是我们要找的独数。
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        //XOR是所有数字的异或结果:
        //因为异或具有交换性,相同的数都异或为0了,所以这个结果其实是两个独数的异或结果,
        int XOR = 0; 
        for( int i = 0; i < data.size(); i++ ){
            XOR ^= data[i];
        }
        //XOR一定不为0,被置1的位表示在这一位上两个独数是不同的(在这位上一个为0,另一个为1)
        //找出这一位(随便哪一位都可以)并设为mask
        int mask = 1; //从最低位开始
        while( (XOR & mask) == 0 ){ 
            mask <<= 1; //左移,继续往更高位找
        }
        //用mask来对所有数相与,可以根据结果是否为0将数分为两组,并且组内所有数的异或结果就是独数
        int group1 = 0, group2 = 0;
        for( int i = 0; i < data.size(); i++ ){
            if( (data[i] & mask) == 0 ){ //异或结果为0的组
                group1 ^= data[i]; //组内所有数进行异或操作,最后的异或结果只剩独数
            }else{//异或结果为1的组
                group2 ^= data[i]; 
            }
        }
        *num1 = group1;
        *num2 = group2;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值