算法 24周 260题:只出现一次的数字 III

195 阅读4分钟

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

示例 :

输入: [1,2,1,3,2,5]
输出: [3,5]

注意:

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

第一种解法:Map暴力解法

    public int[] singleNumber(int[] nums) {
        Map<Integer, Integer> map = new HashMap();

        for(int num:nums){
           if(!map.containsKey(num)){
               map.put(num,1);
           }else{
               map.put(num,2);
           }
        }

        ArrayList<Integer> list =new ArrayList();
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            if(entry.getValue()==1){
                list.add(entry.getKey());
            }
        }

        int[] array=new int[list.size()];
        for(int i=0;i<list.size();i++){
            array[i]=list.get(i);
        }
        return array;
    }

成绩就只能呵呵了

优化一下,具体说只有两个元素只出现一次,没有仔细看到。

    public int[] singleNumber(int[] nums) {
        Map<Integer, Integer> map = new HashMap();

        for(int num:nums){
           if(!map.containsKey(num)){
               map.put(num,1);
           }else{
               map.put(num,2);
           }
        }

        int[] array=new int[2];
        int j =0;
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            if(entry.getValue()==1){
                array[j] = entry.getKey();
                j++;
            }
        }
        
        return array;
    }

好了一点点

第二种解法:位运算

题目标签提示到位运算,查了一些资料

异或规则

异或的运算方法是一个二进制运算:
1^1=0
0^0=0
1^0=1
0^1=1
两者相等为0,不等为1.
n^0=n n^n=0,即任何数与0进行异或,为它本身,两个相同的数进行异或运算,会得到0

例如:
1. a ^ b = b ^ a
2. a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
3. d = a ^ b ^ c 可以推出 a = d ^ b ^ c.
4. a ^ b ^ a = b.

从n^0=n n^n=0得出 a ^ b ^ a = b。知道相同数字异或等于0,然后再跟其他数字异或还是不变。 而且看到一些提示“全部异” 思路就是先把所有数都异或,得出最后值就是这两个唯一值的异或,然后通过Map反显求另外一半的逻辑。 这个时候利用例子3原理

    public int[] singleNumber(int[] nums) {
        Map<Integer,Integer> map = new HashMap();
        int[] array = new int[2];
        int temp = 0;
        for(int num: nums){
            temp = temp^num ;
            map.put(num,num);
        }
        int test =0;
        for(int num: nums){
            test = temp^num;
            if(num != test &&map.containsKey(test)){
                array[0] = num;
                array[1] = test;
                break;
            }
        }
        return array;
    }

可惜速度提高3ms,击败百分比还是太少

最优解法:别人的

    public int[] singleNumber(int[] nums) {
        int xor = 0;
        for (int i : nums)// 一样的抵消,不一样的两个数字异或运算结果必定有一位是1
            xor ^= i;

        int mask = xor & (-xor);

        int[] ans = new int[2];
        for (int i : nums) {
            if ((i & mask) == 0)//== 0、 == mask 两种结果
                ans[0] ^= i;
            else
                ans[1] ^= i;
        }

        return ans;
    }

结题思路(别人的)

第一步:

把所有的元素进行异或操作,最终得到一个异或值。因为是不同的两个数字,所以这个值必定不为0;

       int xor = 0;
        for (int num : nums) {
            xor ^= num;
        } 

以[1,2,1,3,2,5]为输入,可以等00000110

第二步:

这个数字和相反数做与运算得到一个二进制位最右边一位为1的数字,(全部异或之后值其实就是两个不同数异或,位置上面为1则表示两个数字在这一位上不同。) 注解:主要这部分没有想到可以通过这种求得到

int mask = xor & (-xor);

00000110 & (11111010) = 00000010 负数则正数取反码+1 = 11111001 + 1

第三步:

通过与这个mask进行与操作,如果为0的分为一个数组,为1的分为另一个数组。这样就把问题降低成了:“有一个数组每个数字都出现两次,有一个数字只出现了一次,求出该数字”。对这两个子问题分别进行全异或就可以得到两个解。也就是最终的数组了。 (注解:该步骤就要求两个数字(在数组中只出现一次)在哪一位不同,然后使用&将这两数分为两组)

        int[] ans = new int[2];
        for (int num : nums) {
            if ( (num & mask) == 0) {
                ans[0] ^= num;
            } else {
                ans[1] ^= num;
            }
        }

使用00000010通过&将数据分为两组。由于上面得知等于1这一位在两个数的数值也不一样,则可以区分成两组。 复杂度分析: 时间复杂度O(N),空间复杂度O(1)