找到数组中出现特定次数数字的问题
找到数组中出现特定次数数字的问题
作者:Grey
原文地址:
问题一
一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数?
解题思路
因为a ^ a = 0
, 所以出现过偶次的数异或结果都是0
,又因为0^a=a
,所以把数组中所有的数做异或
以后的结果,就是出现了奇数次的那个数。
完整代码
public class LeetCode_0136_SingleNumber {
public static int singleNumber(int[] nums) {
int ans = nums[0];
for (int i = 1; i < nums.length; i++) {
ans ^= nums[i];
}
return ans;
}
}
问题二
一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数
题目链接:LeetCode_0260_SingleNumberIII
解题思路
根据问题一的结论,假设数组中a
和b
这两个数出现了奇数次,这个数组中的所有数字异或以后得到的结果一定a^b
因为a
和b
是两种不同的数,所以a^b
的结果一定不等于0。
所以,a^b
的结果如果转换成二进制的话,一定有某位是1。
我们假设a^b
转换成二进制后最右侧位置的1在i
位置,由此可以得出一个结论:a和b的二进制在i位置一定一个为0,一个为1
不妨假设a
的i
位置为0,b
的i
位置为1。
此外,容易得知,整个数组中的数,i
位置为0的数除了a
以外,其他数一定有偶数个, i
位置为1的数除了b
之外,其他数一定有偶数个。
那么我们可以只对i
位置为1的数求异或,最后得到的值一定是b
,然后通过b^(a^b) = a
,可以得到a
的值。
最后只剩下一个问题:如何求一个数最右侧的1呢?
假设 某个数x二进制为:00010010
, 其最右侧的1是:00000010
。
算法是:对于一个数x
来说,它最右侧的1等于x & ((~x) + 1)
或者x & (-x)
所以,如果一个数是a^b
,那么它最右侧的1
就是(a^b) & (~(a^b) + 1)
我们用(a^b) & (~(a^b) + 1)
这个值去和数组中每个值数做与运算,如果与完以后的结果是0,说明这个数i
位置是0,否则说明这个数i
位置是1。我们前面已经得到一个结论,i
位置为0的数除了a
以外,其他数一定有偶数个。所以,用(a^b) & (~(a^b) + 1)
这个值和每个i
位置是0的数组元素做与运算以后,最后的结果一定是a
。 得到a
以后,然后通过a^(a^b) = b
,可以得到b
的值。
完整代码
public class LeetCode_0260_SingleNumberIII {
public static int[] singleNumber(int[] arr) {
int eor = 0;
for (int n : arr) {
eor ^= n;
}
// 假设出现奇数次的两种数为 a和b
// eor = a ^ b
// 获取最右侧的1
int a = 0;
int rightOne = eor & ((~eor) + 1);
for (int n : arr) {
if ((n & rightOne) == 0) {
a ^= n;
}
}
int b = a ^ eor;
return new int[]{a, b};
}
}
当有如下公式计算一个数最右侧的1
以后
x & ((~x) + 1)
我们还可以解决如下问题:
思路:即提取出最右侧的1以后,与目标数进行与运算, 得到一个新的目标数,然后继续提取新目标数的最右侧的1
,如此往复,即可把所有位置的1
都提取出来。
问题三
一个数组中有一种数出现
k
次,其他数都出现了m
次,\(m > 1\), \(k < m\), 找到出现了k
次的数
要求:假设数组中所有数都是 int 类型,额外空间复杂度\(O(1)\),时间复杂度\(O(N)\)
我们可以这样考虑,设置一个32位的数组,
int[] help = new int[32];
遍历原始数组中每个数num的每一个二进制位, 伪代码如下:
for (int num : arr) {
for (int i = 0; i < 32; i++) {
help[i] += num的二进制中i位置的值(只能是0或者1)
}
}
经过以上循环,help
数组就把数组中的所有数的二进制位上的信息累加起来了。
help[0]
表示数组中所有数二进制中0位置的值之和;
help[1]
表示数组中所有数二进制中1位置的值之和;
......
help[31]
表示数组中所有数二进制中31位置的值之和。
然后i
从0
位置开始拿出help[i]
的值,假设help[i]=x
,用x % m
, 如果结果是k
,说明出现k
次的元素在这个位置上是1, 否则,这个出现了k
次的数在i
位置上是0
, 遍历完help
数组,出现k
次元素的每一位信息都拿到了,然后还原出来即可。
关键代码
public static int km(int[] arr, int k, int m) {
int[] bit = new int[32];
// 将arr中的每个元素转换成二进制填充到bit数组中,每个位置上的数字累加
for (int n : arr) {
for (int i = 0; i < 32; i++) {
// 带着符号右移,保存整个数的原始状态
bit[i] += ((n >>> i) & 1);
}
}
int ans = 0;
for (int i = 0; i < 32; i++) {
if (bit[i] % m != 0) {
// 出现了m次的数,bit[i] % m == 0
// 出现了k次的数,bit[i] % m != 0
// 出现了k次的数在i位置是1
ans |= (1 << i);
}
}
return ans;
}
更多
本文来自博客园,作者:Grey Zeng,转载请注明原文链接:https://www.cnblogs.com/greyzeng/p/15385402.html