2023/10/16

146 阅读3分钟

260. 只出现一次的数字 III

算法掌握:

  • 位运算(异或 ^, 取反~,与运算&)

  • 常见的位运算操作:

    • 与运算(&):对两个操作数的每个对应比特位执行 AND 运算,只有当两个位都为 1 时,结果位才为 1。
    • 或运算(|):对两个操作数的每个对应比特位执行 OR 运算,只要其中一个位为 1,结果位就为 1。
    • 异或运算(^):对两个操作数的每个对应比特位执行 XOR 运算,当两个位不同时结果位为 1,否则为 0。
    • 取反运算(~):反转操作数的每个位,将 0 变为 1,将 1 变为 0。
    • 左移运算(<<):将操作数的比特位向左移动指定的位数,低位补 0。
    • 右移运算(>>):将操作数的比特位向右移动指定的位数,高位补符号位(负数补 1,正数补 0)。

解题思路:

本题如果不考虑 线性时间复杂度的算法且使用常数级空间来解决此问题的话, 直接用hash表存储判读即可,本题还是和 136. 只出现一次的数字 差不多的思路,可以先观看 leetcode每日一题 2023/10/14 这篇文章

按照136这种做法我们可以把两个只出现一次的数看成一个整体 x,那么可以得出两个只出现一次的异或和 x = x1 ^ x2;

因为这两个数字不同,所以异或结果一定不为 0,而且这个结果的二进制表示中一定存在至少一位是 1。我们可以选择任意一位是 1 的二进制位,根据这一位是 0 还是 1,将原数组分为两个子数组,一个子数组中包含第 lll 位是 1 的所有数字,另一个子数组中包含第 lll 位是 0 的所有数字。因为除了这两个只出现一次的数字,其他数字都出现了两次,所以这两个子数组分别包含了其中一个只出现一次的数字,而且都包含了出现两次的数字。所以我们可以对这两个子数组分别进行异或运算,就可以得到这两个只出现一次的数字了。

具体实现上,可以先对所有元素进行异或运算,得到 x = x1 ^ x2。然后,我们通过“x & -x”就能取出 x 的二进制表示中最低位的 1 所在的位置,这个位置代表了 x1 和 x2 在二进制下第一个不同的地方。接着根据这个位置把原数组分成两部分,分别异或得到 x1 和 x2。

javacode:

class Solution {
    public int[] singleNumber(int[] nums) {
        int x = 0;
        for (int num : nums) x ^= num;
        
        int lowbit = x & -x;
        int[] res = new int[2];
        for (int num : nums) {
            // 分别对最低位异或
            res[(num & lowbit) == 0 ? 0 : 1] ^= num;
        }
        return res;
    }
}

c++code:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int x = 0;
        for(int num : nums) x ^= num;
        int a = 0, b = 0;
        // 防止溢出
        int low = x == 0x80000000 ? x : x & -x;
        for(int num : nums){
            if(num & low) a ^= num;
            else b ^= num;
        }
        return {a, b};
    }
};