只出现一次的数(三种类型题)
1. 给一个非空数组,其中只有一个元素出现一次,其余元素出现两次,找出那个出现一次的数:
比如:
输入:nums = [2,2,1]
输出:1
1.1 思路
要判断这个只出现一次的数和出现两次的数,我们可以用按位异或来解决,首先复习一下这个运算符:
在C++中,按位异或运算符用于对两个操作数的每个对应位进行按位异或运算。在C++中,按位异或运算符表示为^
。
具体规则如下:
- 0 ^ 0 = 0
- 0 ^ 1 = 1
- 1 ^ 0 = 1
- 1 ^ 1 = 0
例如,考虑两个二进制数a = 1010
和b = 1100
,进行按位异或运算时,对应位上的操作如下:
a = 1 0 1 0
b = 1 1 0 0
---------
0 1 1 0
所以,a ^ b
的结果为0110
,即十进制数6。
此外,按位异或运算还有一些特性,例如对两个相同的值进行按位异或操作结果为0,对任何值与0进行按位异或操作结果不变等。
就比如;
5^5=0
0^3=3
看到这里我们发现只要让给的那组数按位与一遍,最终的结果就是只出现一次的数
1.2 程序代码
class Solution {
public:
int singleNumber(vector<int>& nums) {
size_t val=0;
for(auto e:nums)
{
val= val ^ e;
}
return val;
}
};
2. 一次变形题
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
示例 1:
输入:nums = [2,2,3,2]
输出:3
2.1 解题思路
这个问题我们仍然用运算符解决,我们发现第一题的思路在这里行不通,因为三次异或不能消除元素,那我们怎么解决呢,3和1总是有差别的,我们还是把数变为二进制来考虑,标准的C++中,一个整数类型通常占据特定的位数。其中,int类型通常是一个32位的整数类型。
我们先来考虑两个数 2和3
2:0000 0000 0000 0000 0000 0000 0000 0010
3:0000 0000 0000 0000 0000 0000 0000 0101
我们就不如把数组中每个位数上的1统计一下,假如数组里都是出现3次,那是不是我们记录的1的个数肯定是3的倍数,相反如果不是3的倍数,那就是我们要找的目标了。
首先我们要记录,得有一个记录的工具吧,我们定义一个含有32个元素的数组,把对应位置1的个数记录一下,然后那些不是3的倍数的位,就是要找的目标了。
怎么记录呢,我们这样:
for(auto e:nums)
{
for(size_t i=0;i<32;++i)
{
if(e & (1<<i))
{
bitArray[i]++;
}
}
}
看代码。我们遍历第一个数的时候,还得遍历他32位上的1吧 ,对应代码:for(size_t i=0;i<32;++i),然后看怎么找1,就用到按位与& 了,这个运算符是:两个都为1则为1,否则为0,所以我们用1给他按位与(1<<i 表示左移i位),当我们检查第i位上是否为1是,就把1左移i位与之相对应,这样就行了。如果e & (1<<i)为1了,我们就是数组里的第i个元素加上1。
好,现在我们已经得到1个数的数组了,现在我们要把里面不为3的倍数的位挑出来。 if(bitArray[i]%3==1)或者 if(bitArray[i]%3!=0),可是我们还要记录他所在的位数,比如负责记录的数组:
0000 0000 0000 0000 0000 0000 0000 7667
这个说明我们要找的数是:
0000 0000 0000 0000 0000 0000 0000 1001
怎么实现这个过程呢,我们这样:
if(bitArray[i]%3==1)
val |=(1<<i);
这里面 I 是或运算符(有1则为1),val为0,比如我们看到第0位为7符合条件可以运行吧,val32个位上都为0,1左移0位,说明第0位上是0,两相或运算,则val第0位为1吧。好,第二次循环,第1位是6,不符合条件吧,也就没法去和1 或 了,就是0,以此类推。
2.2 程序代码
class Solution {
public:
int singleNumber(vector<int>& nums)
{
int bitArray[32]={0};
//统计出了32个位合计1的个数
for(auto e:nums)
{
for(size_t i=0;i<32;++i)
{
if(e & (1<<i))
{
bitArray[i]++;
}
}
}
int val=0;
//找出3n+1的位,这些位就是只出现一次的数为1的位
for(size_t i=0;i<32;++i)
{
if(bitArray[i]%3==1)
val|=(1<<i);
}
return val;
}
};
2. 第二次变形题
给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
2.1 解题思路
这个题里有两个一次出现的数,其余出现两次,按照前面的方法,我们很容易把出现两次的数排除,可结果却是两个出现一次的数异或。以上面的案例说:
出现一次的数是3和5,两个异或一下:
3:0000 0000 0000 0000 0000 0000 0000 0011
5:0000 0000 0000 0000 0000 0000 0000 0101
…^ 0000 0000 0000 0000 0000 0000 0000 0110
异或是相同为0不同为1,这我们得到的结果可以看出这两个数在哪个位上是不同的,而这正是他们之间的茶差别。3和5在第1位和第2位上不同(第0位相同),我们就在这第1位上做文章(第2位也可以)。
首先我们把整个数组的里数,分成两组。怎么分呢,就是把第1位是1的分到第一组,为0的分到第二组。这样是不是就把3和5分到不同的组了,其他的数也按照这个方法,
结果就是这样了:
第一组:2 2 3
第二组:1 1 5
这不就回到第一个问题了嘛,直接异或,就妥了。
2.2 程序代码
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
//异或完成之后,val就是那两个数据异或的结果
int val = 0;
for(auto e : nums)
{
val^=e;
}
size_t i=0;
// 转化成问题1
for(i=0;i<32;++i)
{
if(val & (1<<i))
break;
}
int num1=0,num2=0;
for(auto e:nums)
{
if(e & (1<<i))
num1^=e;
else
num2^=e;
}
vector<int> v;
v.push_back(num1);
v.push_back(num2);
return v;
}
};