题目:输入一个整数数组,数组中只有一个数字出现了一次,而其他数字都出现了3次。请找出那个只出现一次的数字。例如,如果输入的数组为[0,1,0,1,0,1,100],则只出现一次的数字是100。
该题目有个简化的类似题目“输入数组中除一个数字只出现一次之外其他数字都出现两次,请找出那个只出现一次的数字”。对于这个简化版本,有三种方法可以解决,第一种就是两次for循环,第二种可以利用hashset的不重复的特点,第三种是利用异或(^)运算。这里稍稍赘述异或运算的一点细节:
0^0=0
0^a=a
a^a=0
a^b^a=b
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class SingleNumber01 {
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]);
System.out.println(findNum1(arr));
System.out.println("-----------------");
System.out.println(findNum2(arr));
System.out.println("-----------------");
System.out.println(findNum3(arr));
}
public static Integer findNum1(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;
}
public static Integer findNum2(int[] arr) {
Set<Integer> set = new HashSet<>();
//增强for循环遍历数组
for (int i : arr) {
//添加失败,说明集合中存在重复元素
if (!set.add(i))
//移除该重复元素
set.remove(i);
}
if (set.isEmpty()) return null;
return set.toArray(new Integer[0])[0];
}
public static int findNum3(int[] arr) {
int flag = 0;
for (int i : arr) {
flag ^= i;
}
return flag;
}
}
但对于此题目,三个相同数字异或运算并不能消除该数字,所以需要更换方法。
三个相同数字,它们的和一定是三的倍数(有点废话哈哈),换言之,余数一定为0。由此,不难想到其二进制形式也该满足这个道理,任何数位相加之和能被3整除。所以在该题目中,若所有数字第i个数位之和被3整除,说明这个只出现一次的数字第i个数位一定是0。同理,若所有数字第i个数位之和被3除余1,说明这个只出现一次的数字第i个数位一定是1。基此得出:
import java.util.Scanner;
public class SingleNumber02 {
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]);
System.out.println(singleNumber(arr));
}
public static int singleNumber(int[] arr) {
//用于保存arr中所有整数的二进制形式第i个数位之和
int[] bitSums = new int[32];
for (int num : arr) {
for (int i = 0; i < 32; i++) {
//整数num先右移31-i位,原来从左数第i个数位右移后位于最右边
//然后与1做位与运算,即%2运算
bitSums[i] += (num >> (31 - i) & 1);
}
}
int result = 0;
for (int i = 0; i < 32; i++) {
//从高位初步恢复该只出现一次的数字
result = (result << 1) + bitSums[i] % 3;
}
return result;
}
}
由此,同类型的题目“输入一个整数数组,数组中只有一个数字出现m次,其他数字都出现n次。请找除那个唯一出现m次的数字。假设m不能被n整除”都可以用这种方法:如果数组中所有的数字的第i个数位相加之和能被n整除,那么出现m次的数字的第i个数位一定是0;否则出现m次的数字的第i个数位一定是1。