给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
如果不考虑时间复杂度和空间复杂度的限制,这道题有很多种解法,可能的解法有如下几种。
如何才能做到线性时间复杂度和常数空间复杂度呢?
答案是使用位运算。对于这道题,可使用异或运算 ⊕。异或运算有以下三个性质。
class Solution {
public int singleNumber(int[] nums) {
/**写法一:
一个数和它本身做异或运算结果为 0:a ^ a = 0;
一个数和 0 做异或运算的结果为它本身: a ^ 0 = a
*/
/*
int single = 0;
for(int num:nums){
single ^=num;
}
return single;
*/
//写法二:流
return Arrays.stream(nums).reduce(0,(a,b)-> a^b);
}
}
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
由于数组中的元素都在 int(即 32 位整数)范围内,因此我们可以依次计算答案的每一个二进制位是 0还是 1。
具体地,考虑答案的第 i 个二进制位(i从 0 开始编号),它可能为 0或 1。对于数组中非答案的元素,每一个元素都出现了 3 次,对应着第 i 个二进制位的 3个 0 或 3 个 1,无论是哪一种情况,它们的和都是 3 的倍数(即和为 0或 3)。因此:
答案的第 i个二进制位就是数组中所有元素的第 i 个二进制位之和除以 3 的余数。
这样一来,对于数组中的每一个元素 x,我们使用位运算 (x >> i) & 1 得到 x的第 i个二进制位,并将它们相加再对 3取余,得到的结果一定为 0 或 1,即为答案的第 i个二进制位。
class Solution {
public int singleNumber(int[] nums) {
/**方法一:哈希表
Map map = new HashMap();
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
int ans = 0;
for(Map.Entry entry : map.entrySet()){
if (entry.getValue()==1){
ans = entry.getKey();
break;
}
}
return ans;
*/
/**方法二:位运算
*/
int ans = 0;
for (int i = 0; i < 32; ++i) {
int total = 0;
for (int num: nums) {
total += ((num >> i) & 1);
}
if (total % 3 != 0) {
ans |= (1 << i);
}
}
return ans;
}
}
给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
方法一:哈希表
我们可以使用一个哈希映射统计数组中每一个元素出现的次数。
在统计完成后,我们对哈希映射进行遍历,将所有只出现了一次的数放入答案中。
时间复杂度:O(n),其中 n是数组 nums的长度。
空间复杂度:O(n),即为哈希映射需要使用的空间。
方法二:位运算
如何才能做到线性时间复杂度和常数空间复杂度呢?
答案是使用位运算。对于这道题,可使用异或运算 ⊕。异或运算有以下三个性质。
假设数组 nums 中只出现一次的元素分别是 x1和 x2 。如果把 nums中的所有元素全部异或起来,得到结果 x,那么一定有:
x=x1⊕x2
其中 ⊕ 表示异或运算。这是因为 nums 中出现两次的元素都会因为异或运算的性质 a⊕b⊕b=a抵消掉,那么最终的结果就只剩下 x1和 x2的异或和。
x 显然不会等于 0,因为如果 x=0,那么说明 x1=x2,这样 x1和 x2就不是只出现一次的数字了。因此,我们可以使用位运算 x & -x取出 x 的二进制表示中最低位那个 1,设其为第 l 位,那么 x1 和 x2中的某一个数的二进制表示的第 l 位为 0,另一个数的二进制表示的第 l 位为 1。在这种情况下,x1⊕x2的二进制表示的第 l位才能为 1。
这样一来,我们就可以把 nums中的所有元素分成两类,其中一类包含所有二进制表示的第 l 位为 0 的数,另一类包含所有二进制表示的第 l 位为 1的数。可以发现:
对于任意一个在数组 nums中出现两次的元素,该元素的两次出现会被包含在同一类中;
对于任意一个在数组 nums中只出现了一次的元素,即 x1和 x2 ,它们会被包含在不同类中。
因此,如果我们将每一类的元素全部异或起来,那么其中一类会得到 x1
,另一类会得到 x2 。这样我们就找出了这两个只出现一次的元素。
时间复杂度:O(n),其中 n 是数组 nums 的长度。
空间复杂度:O(1)。
class Solution {
public int[] singleNumber(int[] nums) {
/**
方法一:哈希
每个值作key,出现次数为value
取value为1的key
Map map = new HashMap();
int[] ans = new int[2];
for(int i=0;i entry : map.entrySet()){
if(entry.getValue()==1){
ans[k++] = entry.getKey();
}
}
return ans;
*/
/**
方法二:位运算
异或运算的性质:a⊕a=0,a⊕0=a, a⊕b⊕b=a;
因此,所有元素异或后:x=x1⊕x2
*/
int x12 = 0;
for(int num:nums){
x12 ^=num;
}
//防止溢出
int lsb = (x12 == Integer.MIN_VALUE ? x12 : x12 &(-x12));
int type1 = 0,type2=0;
for(int num:nums){
if((num&lsb) !=0){
type1 ^=num;
}else{
type2 ^= num;
}
}
return new int[]{type1,type2};
}
}