• 每日一题


    题目信息

    • 时间: 2019-07-05

    • 题目链接:Leetcode

    • tag:位运算

    • 难易程度:中等

    • 题目描述:

      一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

    示例1:

    输入:nums = [4,1,4,6]
    输出:[1,6] 或 [6,1]
    

    示例2:

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

    注意

    1. 2 <= nums.length <= 10000
    

    解题思路

    本题难点

    查找数组中两个只出现一次的数字,性能最优。

    具体思路

    异或运算

    • 交换律:p⊕q=q⊕p
    • 结合律:p⊕(q⊕r)=(p⊕q)⊕r
    • 恒等率:p⊕0=p
    • 归零率:p⊕p=0

    如果有若干个数字进行异或操作:根据 交换律、结合律 将相同的数字优先两两进行异或运算。此时就只剩下只出现一次的那个数了!

    由于数组中存在着两个数字不重复的情况,我们将所有的数字异或操作起来,最终得到的结果是这两个数字的异或结果:(相同的两个数字相互异或,值为0)) 最后结果一定不为0,因为有两个数字不重复。

    此时的难点在于,对两个不同数字的分组。

    通过 & 运算来判断一位数字不同即可分为两组,那么我们随便两个不同的数字二进制上至少也有一位不同吧!
    我们只需要找出那位二进制上不同的数字mask,即可完成分组( & mask )操作。

    不同数字mask查找演示:

    num1:       101110      110     1111
    num2:       111110      001     1001
    num1^num2:  010000      111     0110
    
    可行的mask:  010000      001     0010
                            010     0100
                            100     
    

    所有的可行 mask 个数,都与异或后1的位数有关。

    提示:为了操作方便,我们只去找最低位的mask:

    代码

    lass Solution {
        public int[] singleNumbers(int[] nums) {
           //用于将所有的数异或起来
            int k = 0;
            for(int num : nums){
                k ^= num;
            }
          //获得k中最低位的1
            int mask = 1;
            while((mask & k) == 0){
                mask <<= 1;
            }
    
            int a = 0;
            int b = 0;
          //将数组分为包含不同数字的两组
            for(int num : nums){
                if((num & mask) == 0){
                    a ^= num;
                }else{
                    b ^= num;
                }
            }
            return new int[]{a,b};
        }
    }
    

    复杂度分析:

    • 时间复杂度 O(N) :只需要遍历数组两次。
    • 空间复杂度 O(1) : 只需要常数的空间存放若干变量。

    其他优秀解答

    解题思路

    通过二分查找,将包含两不同数字的数组分组。

    异或和为 0其实并不代表 要找的元素不在这里面,因为有可能 0只出现了 1次! 所以这种思路需要特判一下某个数为 0的情况。

    代码

    class Solution {
        public int[] singleNumbers(int[] nums) {
            int sum = 0, min = Integer.MAX_VALUE, max = Integer.MIN_VALUE, zeroCount = 0;
            for (int num: nums) {
                if (num == 0) {
                    zeroCount += 1;
                }
                min = Math.min(min, num);
                max = Math.max(max, num);
                sum ^= num;
            }
            // 需要特判一下某个数是0的情况。
            if (zeroCount == 1) {
                return new int[]{sum, 0};
            }
            int lo = min, hi = max;
            while (lo <= hi) {
                // 根据 lo 的正负性来判断二分位置怎么写,防止越界。
                int mid = (lo < 0 && hi > 0)? (lo + hi) >> 1: lo + (hi - lo) / 2;
                int loSum = 0, hiSum = 0;
                for (int num: nums) {
                    if (num <= mid) {
                        loSum ^= num;
                    } else {
                        hiSum ^= num;
                    }
                }
                if (loSum != 0 && hiSum != 0) {
                    // 两个都不为0,说明 p 和 q 分别落到2个数组里了。
                    return new int[] {loSum, hiSum};
                }
                if (loSum == 0) {
                    // 说明 p 和 q 都比 mid 大,所以比 mid 小的数的异或和变为0了。
                    lo = mid + 1;
                } else {
                    // 说明 p 和 q 都不超过 mid
                    hi = mid - 1;
                }
            }
            // 其实如果输入是符合要求的,程序不会执行到这里,为了防止compile error加一下
            return null;
        }
    }
    
  • 相关阅读:
    Python
    deleted
    deleted
    ZOJ 3593 One Person Game(ExGcd + 最优解)题解
    ZOJ 3609 Modular Inverse(扩展欧几里得)题解
    P2234 [HNOI2002]营业额统计(Splay树)题解
    FJUT seventh的tired树上路径(01字典树)题解
    HDU 4825 Xor Sum(01字典树)题解
    Newcoder Metropolis(多源最短路 + Dijkstra堆优化)题解
    HDU 5938 Four Operations(乱搞)题解
  • 原文地址:https://www.cnblogs.com/ID-Wangqiang/p/13245655.html
Copyright © 2020-2023  润新知