题目
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个 元素均出现两次。找出那个只出现了一次的元素。
- 示例
- 示例1:
输入:[2,2,1] 输出:1 - 示例2:
输入:[4,1,2,1,2] 输出:4
- 示例1:
题目分析
注意输入格式,在主方法中不能直接得到数组,应先定义字符串变量接收键盘输入,在使用String类的几种方法得到这个整数数组。
另外定义一个方法找只出现了一次的元素,这个方法返回值为int类型。在方法中,要找到那个元素,有以下解决方法:
方法1
最基本的方法就是双重循环遍历数组,可以直接找出只出现了一次的数,但是,显然这种方法时间复杂度高,效率低下
方法2
使用Set集合存储,Set集合不存储重复值,add()方法返回值为boolean类型,这一特点可以利用。
Set集合的add方法,添加成功返回true,否则返回false,而Set集合不存储重复值,所以当要添加的数与集合中已存在的数重复时,不会再进行添加操作,返回false,这时再进行remove操作,将集合中已存在的那个与要添加的数相同的元素移除,这样将作为方法参数传递过来的整型数组遍历完后,到最后集合中就只剩下了那个只出现了一次的数字。
方法3
使用异或运算符^,0与其他数字异或的结果是那个数字,相等的数字异或得0。要操作的数组中除了某个数字只出现了一次之外,其他数字都出现了两次,所以可以定义一个变量赋初始值为0,用这个变量与数组中每个数字做异或运算,并将这个变量值更新为那个运算结果,直到数组遍历完毕,最后得到的变量的值就是数组中只出现了一次的数字了。这种方法只需遍历一次数组,提高了程序运行的效率。
总结一下:这种解法要用到异或运算的几个规则:
- 0^0 = 0;
- 0^a = a;
- a^a = 0;
- a ^ b ^ a = b.
当然这些只是一部分,不过刚好够用,想要了解异或运算符的小伙伴可以去看看百度百科的介绍:异或运算
代码实现
主方法
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
str = str.substring(1, str.length()-1);//去掉键盘输入的字符串中的"["和"]"
String[] s = str.split(",");//将字符串以","分割,转为一个字符串数组
int[] arr = new int[s.length];//定义一个整形数组,准备用来存数据了
for(int i = 0; i < arr.length; i++)
arr[i] = Integer.parseInt(s[i]);//将字符串转为int类型数据存入整型数组中
System.out.println(Find_Num(arr));//调方法
}
注:这里直接将字符串转为整型数据使用的是包装类Integer类的parseInt()静态方法,调用时直接类名调用,方法返回值为Integer类型,但是可以自动拆箱为int型,因此用int或Integer类型接收均可。
另外,注意一下主方法中没有对方法返回值进行判断而直接做了输出,也就是说不考虑那个数字不出现的情况
方法1
第一种:使用计数器的
public static Integer Find_Num_1(int[] arr){
for(int i = 0; i < arr.length; i++) {
int count = 1;//计数器
for(int j = 0; j < arr.length; j++) {
if(i == j)
continue;//下标相等则直接进入下一次循环
if(arr[i] == arr[j])
count++;//如果不同下标的数组元素相等,则计数器加1
}
if(count == 1)
return arr[i];//返回只出现了一次的元素
}
return null;//找不到则返回null
}
第二种:不使用计数器
public static Integer Find_Num_1(int[] arr){
for(int i = 0; i < arr.length; i++){
for(int j = 0; j <= arr.length; j++){
//注意循环结束的条件,实际运行时j不会超出数组范围
if(i == j)
continue;
if(j == arr.length)
//因为这里的判断,让代码运行时在超出范围前就结束了循环
return arr[i];
if(arr[i] == arr[j])
break;
}
}
return null;//找不到则返回null
}
可能已经有人注意到,方法1中两段代码方法的返回值为Integer类型,这样就方便了在没有找到时返回null作为标志
方法2
public static int Find_Num_2(int[] arr) {
Set<Integer> set = new HashSet<Integer>();//创建Set集合
for(int i : arr) {//增强for循环遍历数组
if(!set.add(i))//添加不成功返回false,前加上!运算符变为true
set.remove(i);//移除集合中与这个要添加的数重复的元素
}
return set.toArray(new Integer[set.size()])[0];
//返回它转为Integer数组后数组的第一个元素
}
使用时要注意前提,必须存在那个只出现了一次的数字,否则Set集合长度将为0,最后一句代码运行时出错,改进一下的话,就像方法1那样,将返回值类型设置为Integer:
public static Integer Find_Num_2(int[] arr) {
Set<Integer> set = new HashSet<Integer>();//创建Set集合
for(int i : arr) {//增强for循环遍历数组
if(!set.add(i))//添加不成功返回false,前加上!运算符变为true
set.remove(i);//移除集合中与这个要添加的数重复的元素
}
if(set.size() == 0) return null;
//如果Set集合长度为0,返回null表示没找到
return set.toArray(new Integer[set.size()])[0];
}
方法3
public static int Find_Num_3(int[] arr) {
int flag = 0;
for(int i : arr) {
flag ^= i;
}
return flag;
}
这段代码的问题在于,如果用户输入了偶数个数的数据,方法将返回0值,而当这个只出现了一次的数字为0时,方法当然也返回0,所以使用前提是必须存在一个只出现了一次的数字
总结
需要注意的是: 这个题的前提条件是这个只出现了一次的数字是存在的,那么使用时在输入阶段需要用户注意到这个问题,输入的数据个数只能是奇数,否则在使用方法3时会出现答案错误的情况,而方法1和改进后的方法2可以正常使用,如果输出为null代表着没有找到。