import java.util.*;

/**
 * NC75 数组中只出现一次的两个数字
 * @author d3y1
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param nums int整型一维数组
     * @return int整型一维数组
     */
    public int[] FindNumsAppearOnce (int[] nums) {
        // return solution1(nums);
        // return solution2(nums);
        return solution3(nums);
    }

    /**
     * 哈希: HashMap
     * @param nums
     * @return
     */
    private int[] solution1(int[] nums){
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int num: nums){
            map.put(num, map.getOrDefault(num, 0)+1);
            if(map.get(num) == 2){
                map.remove(num);
            }
        }

        int[] result = new int[map.size()];
        int i = 0;
        for(int key: map.keySet()){
            result[i++] = key;
        }
        Arrays.sort(result);

        return result;
    }

    /**
     * 哈希: HashSet
     * @param nums
     * @return
     */
    private int[] solution2(int[] nums){
        HashSet<Integer> set = new HashSet<>();
        for(int num: nums){
            if(set.contains(num)){
                set.remove(num);
            }else{
                set.add(num);
            }
        }

        int[] result = new int[set.size()];
        int i = 0;
        for(int key: set){
            result[i++] = key;
        }
        Arrays.sort(result);

        return result;
    }

    /**
     * 位运算
     *
     * lowBit 整数n在二进制表示下最低位1及后面的0所构成的数值
     * 参考树状数组(Binary Indexed Tree)(也称Fenwick树)
     * 参考 -> NC360 右侧更小数     [nowcoder]
     * 参考 -> NC349 计算数组的小和  [nowcoder]
     *
     * 示例:
     * lowBit(44) = lowBit(101100[2进制]) = 100[2进制] = 4[10进制]
     *
     *         n    101100
     * 取反:   ~n    010011
     * 取反+1: ~n+1  010100
     *
     * ~n+1 = -n (计算机存储使用补码, 取反加1后的值即为负的这个数)
     *
     * lowBit(44) = n & (~n+1) = n & (-n) = 44 & (-44)
     *
     *
     * 
     * 假设数组 nums 中只出现一次的元素分别是 x1 和 x2
     * 如果把 nums 中的所有元素全部异或起来, 得到结果 xorsum, 那么一定有:
     * xorsum = x1 ^ x2
     *
     * 其中^表示异或运算, 这是因为 nums 中出现两次的元素都会因为异或运算的性质 a^b^b=a 抵消掉, 那么最终的结果就只剩下x1和x2的异或和(x1^x2)
     *
     * xorsum 显然不会等于0, 因为如果 xorsum=0, 那么说明x1=x2, 这样x1和x2就不是只出现一次的数字了(且xorsum=0时, lowBit=0&(-0)=0, 后续也无法分类)
     * 因此, 我们可以使用位运算 xorsum&-xorsum 取出 xorsum 的二进制表示中最低位那个1
     * 设其为第l位, 那么x1和x2中的某一个数的二进制表示的第l位为0, 且另一个数的二进制表示的第l位为1
     * 因为只有在这种情况下, x1^x2二进制表示的第l位才能为1
     *
     * 这样一来, 我们就可以把 nums 中的所有元素分成两类, 其中一类包含所有二进制表示的第l位为0的数, 另一类包含所有二进制表示的第l位为1的数
     * 可以发现:
     * 对于任意一个在数组 nums 中出现两次的元素, 该元素的两次出现都会被包含在同一类中;
     * 对于任意一个在数组 nums 中只出现了一次的元素, 即x1和x2, 它们会被包含在不同类中.
     *
     * 因此, 如果我们将每一类的元素全部异或起来, 那么其中一类会得到x1, 另一类会得到x2
     * 这样我们就找出了这两个只出现一次的元素.
     *
     * @param nums
     * @return
     */
    private int[] solution3(int[] nums){
        // 异或和
        int xorsum = 0;
        for(int num: nums){
            xorsum ^= num;
        }

        // 防溢出 8位有符 表示范围: 最大值01111111(127) 最小值10000000(-128)
        // xorsum = -128 => -xorsum = 128 -> 超出表示范围(溢出)
        int lowBit = (xorsum==Integer.MIN_VALUE) ? xorsum: xorsum&(-xorsum);
        // 两类
        int type1=0,type2=0;
        for(int num: nums){
            // 二进制表示的第l位为0的数
            if((num&lowBit) == 0){
                type1 ^= num;
            }
            // 二进制表示的第l位为1的数
            else{
                type2 ^= num;
            }
        }

        int[] result = new int[]{type1, type2};
        Arrays.sort(result);

        return result;
    }
}