目录
题目地址
原题目
方法一 遍历数组
看到 Single Number III,首先想到了之前做过的 Single Number,知道用位运算应该会更好的解决问题,可惜想了很久也没有头绪,只能先用笨方法挨个遍历去解决。
解题过程
- 先给数组排序,使得两个相同的数挨在一起
- 遍历数组,比较相邻的两个数 nums[i] nums[i+1] 是否相同,相同则跳过这两个数继续遍历
- 如果这两个数不同,说明第一个数nums[i] 是Single Number,用left记录其位置
- 在比较第二个数nums[i+1] 与第三个数 nums[i+2]是否相同,不同则说明第二个数nums[i+1]也是单独出现的数,返回这两个数结束遍历
- 相同则说明第二个数nums[i+1] 与第三个数 nums[i+2]不是Single Number,跳过继续遍历
代码实现
class Solution {
public int[] singleNumber(int[] nums) {
Arrays.sort(nums);
int left =-1;
for(int i=0;i<nums.length;i+=2){
// 遍历到了最后一个元素,则它一定是 Single Number
if(i >=nums.length-1 || nums[i]!=nums[i+1]){
// 判断是否已经找到了第一个 Single Number
if(left==-1){
left = i;
// 一直到了最后第二个元素还没有找到任何一个 Single Number,
// 则最后两个元素一定都是 Single Number
if(i >=nums.length-2 || nums[i+1]!=nums[i+2]){
return new int[]{nums[i],nums[i+1]};
}
}else{
return new int[]{nums[left],nums[i]};
}
// 只有第一个元素nums[i]是 Single Number 时,连续跳过三个元素
i++;
}
}
return new int[2];
}
}
复杂度分析
时间复杂度:O(n*logn) 快速排序( +O(n) 的时间遍历输入数组)。
空间复杂度:快速排序 O(logn)。
方法二 位运算
然后找到了位运算的解法 >> 数组中不重复的两个元素
>> 异或运算
解题过程
- 全部元素异或消掉出现两次的数字,则异或结果diff 等于两个Single Number的异或结果
- 两个不相等的元素至少有一位存在位级不同,通过diff &= -diff 得到出 diff 最右侧不为 0 的位(即lowbit(diff) ),也就是不存在重复的两个元素在位级表示上的最低位,利用这一位就可以将两个元素区分开来。
- 将所有元素分为两组,一组在此位上是 0,另一组在此位上是 1,而且出现两次的相同数字肯定在同一组
- 同一组的元素再异或消掉同出现两次的数字,剩下的就是各自的Single Number
- 返回两组各自的Single Number
lowbit函数来源于树状数组,其含义是得到该数的二进制从右往左第一个非0位所表示的10进制数。
lowbit 的常用计算方法是 x&(-x) ,x&(-x) 是保留位中最右边的1 ,且将其余的1 设为 0 的方法。
-x = (~x)+1 // 即 补码等于反码加一
例如:6=0110
变为反码后为 0001
再加一为 0010 即它的补码
lowbit(6) = 0110 & 0010 = 0010 = 2
代码实现
public int[] singleNumber(int[] nums) {
int diff = 0;
// 全部元素异或消掉出现两次的数字
for (int num : nums) {
diff ^= num;
}
// 寻找lowbit(diff),这两个数在此位上必然一个是 1 ,一个是 0
diff &= -diff;
int[] ret = new int[2];
for (int num : nums) {
// 将所有元素分为两组,一组在此位上是 0,另一组在此位上是 1,而且出现两次的数字肯定在同一组
// 同一组的元素再异或消掉同出现两次的数字,剩下的就是只出现一次的
if ((num & diff) == 0){
ret[0] ^= num;
} else {
ret[1] ^= num;
}
}
return ret;
}
复杂度分析
-
时间复杂度:O(N)的时间遍历输入数组。
-
空间复杂度:O(1)。
浏览LeetCode提交后的执行用时分布图表发现优化的位运算代码:
class Solution {
public int[] singleNumber(int[] nums) {
if(nums==null || nums.length<2)
return new int[0];
// 所有元素异或
int diff=0;
for(int num:nums) diff^=num;
// 求lowbit(diff)
int mask=diff&(-diff);
int x=0;
for(int num:nums){
// lowbit位上为1的Single Number
if((num & mask)!=0)
x^=num;
}
// 所有元素异或结果与lowbit位上为1的Single Number异或得到另一个Single Number
return new int[]{x,diff^x};
}
}
方法三 哈希表
力扣的一个题解,也是一种比较复杂的方法
来源:只出现一次的数字 III
public int[] singleNumber(int[] nums) {
Map<Integer, Integer> hashmap = new HashMap();
for (int n : nums)
hashmap.put(n, hashmap.getOrDefault(n, 0) + 1);
int[] output = new int[2];
int idx = 0;
for (Map.Entry<Integer, Integer> item : hashmap.entrySet())
if (item.getValue() == 1) output[idx++] = item.getKey();
return output;
}
复杂度分析
- 时间复杂度:O(N)。
- 空间复杂度:O(N),哈希表所使用的空间。