题目描述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
方法一:位运算
注意异或运算的三个特点:利用异或运算的这三个特点,将数组中的所有元素进行异或,那么出现两次的元素就会互相异或为0,只剩下出现一次的元素,该元素与0进行异或,结果仍为该元素,由此可实现空间复杂度O(1)。具体代码如下:
执行结果:通过
执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:38.7 MB, 在所有 Java 提交中击败了26.43%的用户
通过测试用例:61 / 61
class Solution {
public int singleNumber(int[] nums) {//异或
//一个数与0异或=自己
//一个数与自己异或=0
//所以,一个数与自己异或后(为0)又与别的数异或一次,最终会得到那个别的数
int single=0;//a^0=a
for(int num:nums){
single^=num;
}
return single;
}
}
官网中还给出了另外三中解答,分别是用集合存储、哈希表与两倍集合计算,当然相对而言异或这种方法更佳,简单易懂性能高。在此也将给出集合存储与两倍集合计算解答方法的具体过程。
方法二:使用集合存储数字
利用集合判断数字是否存在,若不存在,则add(num);若存在,则remove(num);遍历完整个集合后,最终会剩下那个唯一出现一次的数字。
在这种解法中,遇到一个之前没遇过的问题,就是如何将集合Set中的值取出来,并且转换为int最终返回。一开始觉得很简单,但后来发现Set取值的方法是通过迭代器,迭代结果并不能直接强制转化为int类型;也尝试过使用set.toString最后再转化或截取的方法,但toSting后集合的表现形式是数组,所以需要将[ ]截取掉(去头去尾),截取之后正整数好办,但如果是-1就需要改变截取范围,但截取前又不知道是多少,这就形成了矛盾。最后发现可以直接迭代器访问下一个,问题解决,具体代码如下(注释部分为解决问题过程中用的):
执行结果:通过
执行用时:11 ms, 在所有 Java 提交中击败了14.58%的用户
内存消耗:38.8 MB, 在所有 Java 提交中击败了12.44%的用户
通过测试用例:61 / 61
class Solution {
public int singleNumber(int[] nums) {//利用集合
Set<Integer> single=new HashSet<Integer>();
for(int num:nums){
if(single.contains(num)){
single.remove(num);
}else{
single.add(num);
}
}
//single.toArray();//set.toString()之后也是呈现数组形式
// Integer result=Integer.parseInt(single.toString().substring(1,2));//将数组中的俩[]去掉,适用于正整数
//Integer result=Integer.parseInt(single.toString().substring(1,single.size()+2));//适用于-1
return single.iterator().next();
}
}
上面是我做测试的过程,具体代码如下:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class test {
public static void main(String[] args) {
Set<Integer> set=new HashSet<>();
set.add(-1);
// set.toArray();
// set.toString();
System.out.println(set.size());
System.out.println(set);
System.out.println(set.toString());
System.out.println(set.toString().substring(1,set.size()+2));
Integer result=Integer.parseInt(set.toString().substring(1,set.size()+2));
System.out.println(result);
}
}
方法三:两倍集合计算
这个方法虽然花了很长时间去琢磨,性能上也没有位运算的好,但毕竟是完全靠自己一点点摸索出来的,还是成就感和收获满满!!
思路:
1.利用集合的唯一性,循环判断,所数组中的值不在集合中,则将该值放入集合;若存在,则跳过;
2.一趟循环之后集合中有且仅有包含数组中的所有值一次,并在便利的过程中对数组中的所有值进行累加,得到数组中各个元素值之和sum;
3.之后遍历集合,获取到集合中所有元素之和setsum;
4.最后将setsum的两倍减去sum得到的差值即为仅出现一次的那个数的值。
注意点:
1.由于set集合有序不重复的特点,传入的数组为[4,1,2,1,2],传出的集合为[1, 2, 4],即需要注意数组的第一个元素并不一定就是集合中的第一个元素;
2.第14行代码中,利用set.iterator().next();方法逐一获取set集合中的值,例如数组[4,1,2,1,2],那么利用该方法获取值的最终结果为[1, 2, 4],即会从第一个元素开始获取;
3.在累加集合的过程中,每次获取到值之后,都需要利用set.remove(set.iterator().next());方法将集合中的头元素去掉,以获得集合中的下一个元素。
执行结果:通过
执行用时:636 ms, 在所有 Java 提交中击败了5.95%的用户
内存消耗:39.5 MB, 在所有 Java 提交中击败了5.02%的用户
通过测试用例:61 / 61
class Solution {
public int singleNumber(int[] nums) {//两倍集合判断
Set<Integer> set=new HashSet<>();
int setsum=0;
int sum=0; //Set是有序的,nums中的第一个并不一定是set中的第一个
for(int num:nums){
if(!set.contains(num)){
set.add(num);
}
setsum+=num;//计算数组数值之和
}
int size=set.size();
while(size>0){
sum+=set.iterator().next();
set.remove(set.iterator().next());//之前忘记加这句,搞得一直在加第一个数
size--;
}
return sum*2-setsum;
}
}
以下是找bug的过程中使用的测试代码:
int[] nums={4,1,2,1,2};
Set<Integer> set=new HashSet<>();
int setsum=0;
int sum=0;
for(int num:nums){
if(!set.contains(num)){
set.add(num);
System.out.println("set="+set.toString());
}
setsum+=num;
System.out.println("setsum="+setsum);
}
System.out.println("========");
int size=set.size();
System.out.println(size);
System.out.println(set.toString());
while(size>0){
System.out.println("next="+set.iterator().next());
set.remove(set.iterator().next());
// sum+=set.iterator().next();
// System.out.println("sum="+sum);
size--;
}
System.out.println(sum*2-setsum);
平平无奇小白程序媛一枚,欢迎各位大佬交流指教,如有不正确的地方,欢迎留言改正,谢谢!!!