java-NC75 数组中只出现一次的两个数字
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;
}
}