错误的集合

这道题出自LeetCode,题目如下:

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

给定一个数组 nums 代表了集合 S 发生错误后的结果。

请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

示例 1:

输入: nums = [1,2,2,4]
输出: [2,3]

示例 2:

输入: nums = [1,1]
输出: [1,2]

这道题可以利用二进制位运算的性质巧妙解决。我们假设丢失的数字和重复的数字为a和b。由题目可知,我们有两个序列,一个序列A为[1,2,3,...,a,...,b,...,n],还有一个序列B为[1,2,3,...,b,b,...,n]。我们知道异或运算是满足相同为0不同为1的性质的,一个数异或它自身一定为0。因此,我们可以将两个序列合在一起求一次异或:

A ^ B = a ^ b

我们发现两个序列异或之后的结果就是我们要找的两个数字的异或。让我们再次回到异或的性质中来,两个数异或,相同的二进制位为0,不相同的二进制为1。由于a和b肯定是不相同,那么,a ^ b必定不为0,也就是说它存在一个为1的二进制位。我们假设a ^ b的结果为c。那么,让我们来寻找c最低的二进制为1的位:

d = c & ~(c - 1)

这个也比较好理解,我们假设c的二进制表示为x...x10...0,这里写出来的1就是它最低二进制位为1的位。那么,c - 1的二进制表示为x...x01...1,对其取反得到:~(x...x)10...0,我们可以发现1左边的高位数字一定是原先1左边的高位数字的取反,因此两者求与即可得到:d = 0...010...0,1就是要找的最低二进制为1的位。

那么,有了这个之后,又能做什么呢?我们知道,这个d是可以区分开a和b的,即a和b中,一定有一个数对应的二进制位为0,另外一个为1。回到我们前面说的两个序列,这个d一定可以把两个序列进行划分,一边是二进制位为0的,一边是二进制位为1的,而且a和b一定位于两边。显然,如果这两边各自异或,最后剩下来的,一定一边是a,一边是b。

有了a和b之后,我们需要弄明白哪个是重复的数字,哪个是缺失的数字。这个很简单,只需要遍历一遍包含重复数字的序列,就知道答案了。最后通过的代码如下:

class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        int diff = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            diff ^= (i + 1);
            diff ^= nums[i];
        }

        int pivot = diff & ~(diff - 1);
        int ga = 0, gb = 0;

        for(int i = 0; i < nums.size(); i++)
        {
            if((i + 1) & pivot)
            {
                ga ^= (i + 1);
            }
            else
            {
                gb ^= (i + 1);
            }

            if(nums[i] & pivot)
            {
                ga ^= nums[i];
            }
            else
            {
                gb ^= nums[i];
            }
        }

        vector<int> res;
        for(int i = 0; i < nums.size(); i++)
        {
            if(ga == nums[i])
            {
                res = {ga, gb};
                return res;
            }
        }

        res = {gb, ga};
        return res;
    }
};
posted @ 2021-05-30 15:16  异次元的归来  阅读(59)  评论(0编辑  收藏  举报