题目描述
整数数组 sockets
记录了一个袜子礼盒的颜色分布情况,其中 sockets[i]
表示该袜子的颜色编号。礼盒中除了一款撞色搭配的袜子,每种颜色的袜子均有两只。请设计一个程序,在时间复杂度 O(n),空间复杂度O(1) 内找到这双撞色搭配袜子的两个颜色编号。
示例 1:
输入:sockets = [4, 5, 2, 4, 6, 6] 输出:[2,5] 或 [5,2]
示例 2:
输入:sockets = [1, 2, 4, 1, 4, 3, 12, 3] 输出:[2,12] 或 [12,2]
提示:
2 <= sockets.length <= 10000
NOTE:题目要求时间复杂度 O(N) ,空间复杂度 O(1),因此首先排除 暴力法 和 哈希表统计法
该题也就是说一个整型数组里除了两个数字之外,其他数字都出现了两次,请找出这两个只出现一次的数字
解题思路:利用异或运算^的特点;0与任何数异或等于该数字,任何数与自身异或等于0;且异或运算满足交换律;
解题步骤:
-
异或整个数组:
- 首先,通过对整个数组进行异或操作,你得到了两个只出现一次的数的异或结果。这个结果在二进制中的每个1都表示这两个数在该位上是不同的。
-
确定差异位:
- 接下来,并非与1进行异或,而是通过一个循环找出异或结果中最低位的1。这一步是通过与操作和位移实现的,具体操作是:
- 初始化一个掩码变量
m = 1
(二进制表示为...0001)。 - 通过
while((z & m) == 0)
循环左移这个掩码,直到z & m
的结果不为0。这个结果不为0表示在这一位上,两个数是不同的。
- 初始化一个掩码变量
- 接下来,并非与1进行异或,而是通过一个循环找出异或结果中最低位的1。这一步是通过与操作和位移实现的,具体操作是:
-
分组:
- 使用找到的差异位(掩码
m
),将数组分成两组。这一步利用了与操作:- 如果数字与掩码
m
的与操作结果为0,表示这个数字在掩码m
标记的位上为0。 - 如果与操作结果不为0,表示这个数字在掩码
m
标记的位上为1。
- 如果数字与掩码
- 使用找到的差异位(掩码
-
组内异或操作:
- 对每组数字分别执行异或操作。由于除了一个唯一的数字外,每组的其他数字都出现两次,这意味着组内异或操作将取消所有成对的数字,只留下唯一的那个。
-
结果输出:
- 最后,每组的异或结果就是那两个只出现一次的不同的数字。
假设我们有以下数组:
sockets = [4, 2, 4, 3, 2, 5]
在这个数组中,数字 4
和 2
各出现两次,而数字 3
和 5
各出现一次。我们的目标是找到这两个只出现一次的数字,即 3
和 5
。
步骤 1: 计算全部数字的异或
int n = 0;
for(int i : sockets) {
n ^= i;
}
进行异或操作的结果(n)将是所有数字异或的结果。按照这个例子:
- 开始:
n = 0
n ^= 4
->n = 4
n ^= 2
->n = 6
(二进制110
)n ^= 4
->n = 2
(二进制010
)n ^= 3
->n = 1
(二进制001
)n ^= 2
->n = 3
(二进制011
)n ^= 5
->n = 6
(二进制110
)
步骤 2: 找到第一个设置位(即差异位)
找最低位差异位即可;找差异位的目的就是将数组分为两个数组 因为通过异或只能取出一个不同的数,我们按照这两个数的差异位来分组,就一定可以将两个数分在两个不同的数组中,最后分别异或 就可以取出两个数
int m = 1;
while((n & m) == 0) {
m <<= 1;
}
找到二进制数 n = 6
(110
) 的最低设置位:
n & 1
(110 & 001
) =0
,所以m
左移一位 ->m = 2
(010
)n & 2
(110 & 010
) =2
,循环停止,找到最低设置位是第二位。
步骤 3: 根据设置位分组并再次异或
int x = 0, y = 0;
for(int i : sockets) {
if((i & m) == 0) {
x ^= i;
} else {
y ^= i;
}
}
根据第二位是否设置,将数组分为两组并分别进行异或运算:
4
(二进制100
):第二位为0,所以x ^= 4
2
(二进制010
):第二位为1,所以y ^= 2
4
(二进制100
):第二位为0,所以x ^= 4
,现在x = 0
3
(二进制011
):第二位为1,所以y ^= 3
2
(二进制010
):第二位为1,所以y ^= 2
,现在y = 1
5
(二进制101
):第二位为0,所以x ^= 5
最终结果:x = 5
, y = 1
完整代码:
#include <vector>
using namespace std;
class Solution {
public:
// 函数sockCollocation接受一个整数数组sockets,并返回一个包含两个只出现一次的数字的向量
vector<int> sockCollocation(vector<int>& sockets) {
// 初始化变量n用来存储数组中所有元素的异或结果
int n = 0;
// 遍历数组,对所有元素进行异或操作
// 相同的数字异或会取消彼此,留下的是两个只出现一次的数字的异或结果
for(int i : sockets) {
n ^= i;
}
// 初始化变量m作为掩码,用来找出n中第一个为1的位,即两个单独数字不同的那一位
int m = 1;
// 循环左移m直到找到n的一个为1的位
// 这是因为n的该位为1表示两个只出现一次的数字在这一位上一个为0,一个为1
while((n & m) == 0) {
m <<= 1;
}
// 初始化x和y,用来最终存储两个只出现一次的数字
int x = 0, y = 0;
// 再次遍历数组,使用m将数组分为两组,并分别对这两组的数字进行异或操作
for(int i : sockets) {
// 如果数字i的m位为0,则归入一组
if((i & m) == 0) {
x ^= i;
} else { // 否则归入另一组
y ^= i;
}
}
// 返回只出现一次的两个数字
return vector<int> {x, y};
}
};