剑指 Offer 56 - I. 数组中数字出现的次数
题目描述
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
限制:
-
2
<
=
n
u
m
s
.
l
e
n
g
t
h
<
=
10000
2 <= nums.length <= 10000
2<=nums.length<=10000
哈希表
思路
- 比较简单的思路是利用
map
统计每个元素出现的次数,然后取次数只出现一次的元素,即可。 - 但是这样空间复杂度为 O ( n ) O(n) O(n),不满足题目中空间复杂度为 O ( 1 ) O(1) O(1) 的要求。
位运算
异或运算
- x ⊕ x = 0 x \oplus x = 0 x⊕x=0
- x ⊕ 0 = x x \oplus 0 = x x⊕0=x
- x ⊕ y = y ⊕ x x \oplus y = y \oplus x x⊕y=y⊕x
思路 🤔
- 一遍遍历,求出所有元素异或和
sum
; - 求
sum
二进制中不为 1 1 1 的位置index
; - 根据二进制第
index
不同,将 n u m s nums nums 中所有元素分为 2 组,分别求 异或和,最终的异或和结果x
、y
即为所求结果。
class Solution {
public int[] singleNumbers(int[] nums) {
int sum = 0;
// 1、求所有元素异或和
for (int i : nums) sum ^= i;
// 2、求sum二进制中不为1的位置
int index = -1;
for (int i = 0; i < 32; i++) { // int32位
if ((sum & 1) == 1) {
index = i;
break; // 找到即退出
}
sum >>= 1; // 右移一位
}
System.out.println("index = " + index);
// 3、分两组
int x = 0;
int y = 0;
for (int i : nums) {
// 这里根据二进制第index为是否为 0(而不能是1),来分为两组
// if ((i & (1 << index)) == 1) x ^= i; // error
if ((i & (1 << index)) == 0) x ^= i;
else y ^= i;
}
return new int[] {x, y};
}
}
🔥 注意:(
(i & (1 << index)
)结果为 0 0 0 或者 2 i n d e x 2^{index} 2index(而不是0或者1)
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度:
O
(
1
)
O(1)
O(1)
剑指 Offer 56 - II. 数组中数字出现的次数 II
题目描述
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
限制:
- 1 < = n u m s . l e n g t h < = 10000 1 <= nums.length <= 10000 1<=nums.length<=10000
- 1 < = n u m s [ i ] < 2 31 1 <= nums[i] < 2^{31} 1<=nums[i]<231
哈希表
思路
- 利用
map
统计每个元素出现的次数,然后取次数只出现一次的元素,即可。
class Solution {
public int singleNumber(int[] nums) {
// 统计每个元素出现次数
Map<Integer, Integer> map = new HashMap<>();
for (int i : nums) map.put(i, map.getOrDefault(i, 0) + 1);
System.out.println(map);
// 找到只出现一次的元素
int res = 0;
for (int key : map.keySet()) {
if (map.get(key) == 1) res = key;
}
return res;
}
}
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度:
O
(
n
)
O(n)
O(n)
位运算
参考:K佬题解
思路 🤔
- 对于出现三次的数字,各二进制位 出现的次数都是 3 的倍数。
- 因此,统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字
步骤 💡
- 统计nums所有元素 对应二进制每个位置上1的个数;
- 对count中每个位置1的个数 %3,并将 count[i] % 3 后对应的二进制转化为十进制
class Solution {
public int singleNumber(int[] nums) {
int[] counts = new int[32];
// 1、统计nums所有元素 对应二进制每个位置上1的个数
for (int num :nums) {
for (int i = 0; i < 32; i++) { // int 32bit
if ((num & 1) == 1) {
counts[i]++;
}
num >>>= 1; // 这里不要用有符号右移 >>,如果存在负数时就gg了
}
}
System.out.println(Arrays.toString(counts));
// 2、对count中每个位置1的个数 %3,并将 count[i] % 3 后对应的二进制转化为十进制
int res = 0;
for (int i = counts.length - 1; i >= 0; i--) { // 高位 --> 低位
res <<= 1;
res |= (counts[i] % 3);
}
return res;
}
}
K佬 用的是更优的解法是
位运算 + 有限状态自动机
,暂时还没看