剑指offer系列——40.数组中只出现一次的数字i-ii
Q:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
T:
1.排序后一个一个对比。
2.使用hash。
import java.util.*;
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
Queue<Integer> arr = new LinkedList<Integer>();
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < array.length; i++) {
if (!map.containsKey(array[i])) {
map.put(array[i], 1);
} else {
map.put(array[i], map.get(array[i]) + 1);
}
}
for (int i = 0; i < array.length; i++) {
if (map.get(array[i]) == 1) {
arr.add(array[i]);
}
}
num1[0] = arr.poll();
num2[0] = arr.poll();
System.out.println(num1[0]);
System.out.println(num2[0]);
}
}
3.使用set。java的话可以使用。使用Set集合存储,Set集合不存储重复值,add()方法返回值为boolean类型,这一特点可以利用。
Set集合的add方法,添加成功返回true,否则返回false,而Set集合不存储重复值,所以当要添加的数与集合中已存在的数重复时,不会再进行添加操作,返回false,这时再进行remove操作,将集合中已存在的那个与要添加的数相同的元素移除,这样将作为方法参数传递过来的整型数组遍历完后,到最后集合中就只剩下了那个只出现了一次的数字。
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
HashSet<Integer> set = new HashSet<Integer>();
for (int i = 0;i < array.length;i++){
if(!set.add(array[i])){
set.remove(array[i]);
}
}
Object[] temp =set.toArray();
num1[0] = (int) temp[0];
num2[0] = (int) temp[1];
}
3.使用异或运算寻找。这个方法很巧妙。(感谢@披萨大叔)
位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。
当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。
依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,因为其他数字全部都与自身异或结果为0了。
这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
void FindNumsAppearOnce(vector<int> data, int *num1, int *num2) {
if (data.size() < 2)
return;
if (data.size() == 2) {
*num1 = data[0];
*num2 = data[1];
}
int bitResult = 0;
for (int i = 0; i < data.size(); i++)
bitResult ^= data[i];
//bitResult为AB异或的结果
int index = 0;
while (true) {
//找到第一个1所在的位数,也是区分AB的指标
if ((bitResult & 1) != 0)
break;
bitResult >>= 1;
index++;
}
*num1 = *num2 = 0;
for (int i = 0; i < data.size(); i++) {
//将AB分组,分组后异或,异或的最后结果就是AB
if (isBit1(data[i], index))
*num1 ^= data[i];
else
*num2 ^= data[i];
}
}
bool isBit1(int target, int index) {
int temp = target >> index;
return (temp & 1) != 0;
}
Q:现在有一个整数类型的数组,数组中只有一个元素只出现一次,其余元素都出现三次。你需要找出只出现一次的元素
注意:
你需要给出一个线性时间复杂度的算法,你能在不使用额外内存空间的情况下解决这个问题么?
A:
若一个数出现三次,则其对应的二进制数每一位相加必为3或0。
统计数组中所有元素的每一位,若为3的倍数,所求数的该二进制位对3取余为0,否则为1。
举例:{1,1,1,2,2,2,3}
01
01
01
10
10
10
11
——
04%3 = 01
40%3 = 10
——
11
public int singleNumber(int[] A) {
int result = 0;
for (int i = 0; i < 32; ++i) {
int bits = 0;
for (int value : A) {
bits += (value >> i) & 1;//依次获取A中每个元素的第i位,全部和1与后相加
}
//这里的|=和+=是一样的,因为当前只会得到在i为为0或1,右侧位全为0的值和i位为0但右侧不一定完全为0的值的和,用或和加都行,但用或会更快
result |= (bits % 3) << i;
}
return result;
}
这个题还有种解法,可看 https://www.jianshu.com/p/c8612aef41af
但实在太难了,没看懂。