只出现一次的数字(Ⅰ)(点击链接进入题目)
只出现一次的数字(Ⅱ)(点击链接进入题目)
只出现一次的数字(Ⅲ)(点击链接进入题目)
Ⅰ
题目描述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
题目分析:抓住规律,不管元素有多少个,只有一个数字出现了一次,其它都出现了两次。本题我们借助异或来解决,异或:相同为0,相异为1,且0与任何数异或都为数本身。我们利用异或的规律,遍历整个数组,让数组的所有元素之间相互异或,不管多少个元素,所有出现两次的元素全部都异或为0,只剩下出现一次的元素与多个0进行异或,结果即为答案。
class Solution {
public:
int singleNumber(vector<int>& nums) {
//利用0和任何数都是其本身的特性
vector<int>::iterator it = nums.begin();
while(it != nums.end())
{
ret ^= *it;
++it;
}
return ret;
}
};
Ⅱ
题目描述:
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
示例 1:
输入:nums = [2,2,3,2]
输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99]
输出:99
题目分析:这是找出出现一次数字的第二个版本,这个版本和上个版本相同的是都是有一个元素仅出现一次,并且需要把这个元素找出并输出;不同的是:其余元素出现三次。
我们这样分析,不管数字是什么出现了几次,我们看他的二进制位,将所有元素全部转化为二进制,所有二进制的位数相加,因为数组是int类型,所有共有32个比特位。
我们将所有的二进制位相加,得到如下的结果:
不管数字是几他只要出现,就一定要在二进制中以1的形式和0的形式表现出来,比如数字2出现3次,二进制相加后,在第二位一定是3;比如7出现3次,二进制相加后,第一二三位都是3,所以我们根据相加后的结果,看其与3的关系,如果该位置的值是3N,则说明原数组中某个数字出现三次,且该数字的二进制在该位置为1。如果该位置的值为3N+1,则说明原数组中仅出现一次的那个数字,其二进制在该位置一定为1。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int val = 0;//用来存储值
for(int i = 0; i < 32; i++)
{
//定义一个标志位flag,让其依次左移32次
int flag = 1 << i;
//ret用来存储二进制中某个位置1的个数
int ret = 0;
for(size_t j = 0; j < nums.size(); j++)
{
//nums数组中的每一个数都分别与32个比特位按位与,计算二进制中1的个数
if((nums[j] & flag) != 0)
ret++;
}
//一个bit位统计结束就计算该位置的值与3的关系,如果该值对3求余不为0,
//说明仅出现一次的数字其二进制在该位置一定为1,则将该位置与val进行按位或运算
if((ret % 3) != 0)
val |= flag;
}
return val;
}
};
Ⅲ
题目描述:
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
示例 1:
输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。
示例 2:
输入:nums = [-1,0]
输出:[-1,0]
示例 3:
输入:nums = [0,1]
输出:[1,0]
题目分析:给一个整数数组nums,只有两个元素出现出现一次,其它元素出现两次,我们要做的就是找出这两个仅出现一次的数字,结合Ⅰ的经验,我们发现将数组所有元素进行异或得到的结果就是这两个仅出现一次的元素异或的结果,那么我们该怎么利用这个特性呢?Ⅱ的解题方法能否给我们一些思路呢?
如果将数组中所有二进制的位数相加,发现仅出现两次的数字其二进制位1的位置,所有的1相加一定是奇数,而我们的异或结果的二进制为1的位置刚好对应相加为奇数的位置。
我们这样操作,根据异或结果二进制上1的位置,某个位置为1的元素分为一组,这样分组后就会将两个仅出现一次的元素分为两个组,然后用整个数组异或的结果,分别与这两个数组异或,就会得到结果。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
//定义一个ret用来存放整个数组异或的结果
int ret = 0;
for(size_t i = 0; i< nums.size(); i++)
ret ^= nums[i];
//此时ret中存放的就是两个只出现一次的数字异或的结果
//定义pos变量用来记录ret二进制中哪个位置是为1,用来作为分组的依据
int pos = 0;
for(size_t i = 0; i < 32; i ++)
{
//将1左移32位,分别与ret进行按位与操作,如果得到的结果不是0,则说明此时第i位为1,然后进行分组
int val = 1 << i;
int flag = (ret & val);
if(flag != 0)
//将i的值赋值给pos,此时pos说明ret的二进制中第pos位为1
pos = i;
}
//定义一个m变量用来存储分组后其中一个数组异或的结果
int m = 0;
for(size_t i = 0; i< nums.size(); i++)
{
//遍历整个数组,找出所有符合条件的元素,将其与m进行异或操作,经过操作后
if(((nums[i] >> pos) & 1) == 1)
m ^= nums[i];
}
//for循环后,m存放的值就是分组后,某个分组异或的值
//将ret与m异或就得到其中一个结果num1,然后得到的结果与ret再次异或就得到结果num2
int num1 = m ^ ret;
int num2 = num1 ^ ret;
return {num1, num2};
}
};