只出现一次的数字

136. 只出现一次的数字

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

说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

  1. 注意位 异或运算的性质:
  • 交换律:a ^ b ^ c <=> a ^ c ^ b

  • 任何数和0 相与,为自己 0 ^ n => n

  • 相同的数异或为0: n ^ n => 0

简单的应用:1.交换两个数字

a = a^b;  
b = b^a;  `b = a^b^b=a`
a = a^b;
//var a = [2,3,2,4,4]
//2 ^ 3 ^ 2 ^ 4 ^ 4等价于 2 ^ 2 ^ 4 ^ 4 ^ 3 => 0 ^ 0 ^3 => 3
    int singleNumber(vector<int>& nums) {
        int sign = 0;
        for(auto i:nums)
            sign ^= i;
        return sign;
    }

137. 只出现一次的数字 II

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

说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

输入: [2,2,3,2]
输出: 3
---------
输入: [0,1,0,1,0,1,99]
输出: 99
  1. 我们将每一个int整数当成一个32bit的二进制组合,对于每一位的二进制的1和0累加起来必然是3N或3N+1,为3N代表目标值在这一位没贡献,3N+1代表目标值在这一位有贡献(=1),然后将所有有贡献的位|起来就是结果。这样做的好处是如果题目改成K个一样,只需要把代码改成cnt%k,很通用,.
  2. 时间复杂度为 O(32*N),空间复杂度为O(1)

重点: 使用一个32长度的数组 统计每一位bit二进制的累加状态,然后判断每个二进制位 出现的次数 bits[i] += (nums[j]>>i) & 1; int bits[32];

解法

int singleNumber(vector<int>& nums) {
        vector<int> bits(32,0);
        int ans = 0;
        for (int i = 0; i < 32; i++) {
            for (int j = 0; j < nums.size(); j++)
                bits[i] += (nums[j]>>i) & 1;
            ans |= (bits[i] % 3) << i;
         }
      return ans;
    }

260. 只出现一次的数字 III

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

输入: [1,2,1,3,2,5]
输出: [3,5]
# 结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
# 你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

思路

  1. 有两个数只出现了一次记为 x、y, 初始化为 0, 其余的数出现了两次,
    我们可以对所有的数进行一次异或操作, 易知这个结果就等于 x 和 y
    的异或结果 x^y (相同的数异或结果都为 0, 0和任意数异或结果都为那个数).

  2. 那么我可以考虑异或结果的某个非 0 位,如最后一个非 0 位, 因为我们知道只
    有当 x、y 在该位不一样的时候才会出现异或结果为 1. 所以我们以该
    位是否为 1 对数组进行划分**(这样就划分成两个数组,每个数组序列中都存在一个只出现一次的数字,既可以使用简单的异或直接找到结果)**, 只要该位为 1 就和 x 异或, 只要该位为 0就和 y 异或, 这样最终得到就是只出现过一次的两个数(其他在该位为 1 或0 的数必然出现 0/2 次对异或结果无影响)

解法

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        vector<int> res(2,0);
        int ans = 0;
        for(auto i:nums)
            ans ^= i;
        
        int bits = 1;
        // 寻找二进制表示中 最右边不为0 的位置
        // 例: ans= 0110 则bits=0010
        while((ans&1) == 0){
            ans >>= 1;
            bits <<= 1;
        }
        // 将原有的数组分为两部分,每部分中出现一个不重复元素,就可以用 异或 找到这个数字
        for(auto i:nums){
            if((i & bits) == 0 )
                res[0] ^= i;
            else
                res[1] ^= i;
        }
        return res;
    }
};

解法2

  1. 使用hash_map统计每个数字出现的次数,<数值,次数>,然后统计出现一次的数字,需要较多的空间存储状态

vector<int> singleNumber(vector<int>& nums) {
        unordered_map<int,int> numbers;
        for (int number : nums)
        {
            if (!numbers.insert(pair<int,int>(number,1)).second)
                numbers.erase(number);;//重复便移除
        }
        
        vector<int> res;
         for(auto i:numbers){
                res.push_back(i.first);
        }
        return res;
    }

645. 错误的集合(拓展)

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

给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。

示例 1:
输入: nums = [1,2,2,4]
输出: [2,3]
# 数组无序

思路

  1. 思考一个简单的问题,一个长度为 n-1的数组包含 1 到 n 中的 n−1 个数字,有一个数字缺失,如何找出这个缺失的数字。首先使用 1 到 n 的所有数字做异或运算,然后再使用数组中的所有数字与这个数字异或,最后得到的值就是缺失数字。因为数字与其本身做异或运算结果为 0,因此所有数字做完异或后,剩余的就是缺失的数字。

{1,2,2,4} {1,2,3,4} ans = (1^1) ^ (2 ^2) ^ (2 ^3) ^ (4 ^4) = (2 ^ 3)

  1. 按照这个方法,将 nums 中所有的数字与 1 到 n 的所有数字做异或运算,得到的结果为 x^y,x和 y 分别表示 nums 中重复数字和缺失数字。

  2. 在异或结果 x^y 的二进制中,值为 1 的位置表示 x 和 y 在该位置的值不同,值为 0 的位置表示 x 和 y 在该位置的值相同。我们称 x ^y 最右边比特位为 rightmostbit,且使该位置为 1。

  3. 根据 rightmostbit不同将数组 nums 分为两部分。第一部分所有数字的 rightmostbit 为 1,第二部分所有数字的 rightmostbit 为 0。那么 x 和 y 会被分配到不同的部分。此时问题就简化为最开始的简单问题。

vector<int> findErrorNums(vector<int>& nums) {
        int ans = 0;
        int xor1=0,xor0=0;
        int len = nums.size();

        for(int i=0;i<len;i++){
            ans ^= nums[i];
            ans ^= i+1;
        }
        
        // int bits = ans & ~(ans - 1);
        int bits = 1;
        while((ans&1) == 0){
            ans >>= 1;
            bits <<= 1;
        }

        for(int n:nums){
            if ((n&bits) != 0)
                xor0 ^= n;
            else
                xor1 ^= n;
        }

        for(int i=1;i<=len;i++){
            if ((i&bits) != 0)
                xor0 ^= i;
            else
                xor1 ^= i;
        }
		// 先重复数字 再缺失数字
        for(int i:nums){
            if(xor0 == i)
                return vector<int> {xor0,xor1};
        }
        return vector<int> {xor1,xor0};
    }
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务