只出现一次的数字
只出现一次的数字 I
给你一个 非空 整数数组 nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
思路1:
其实可以通过计数的思路去实现这个题。但是题目要求时间复杂度是O(N),因此这个思路就看看就行
代码如下:
int singleNumber(vector<int>& nums)
{
int count = 0; // 统计该数字出现了1次还是2次
int ret = 0; // 用于返回只出现一次的数字
for (int i = 0; i < nums.size(); i++)
{
int tmp = nums[i]; // tmp记载第i个元素
for (int j = 0; j < nums.size(); j++)
{
if (tmp == nums[j])
count++;// 只要找到相同的就+1
}
if (count == 1) // 只要是count==1的情况下,就说明这个tmp就是只出现一次的数字
ret = tmp;
count = 0; // 重置
}
return ret;
}
思路2:
除了计数法,我们还可以通过异或的思想去解决,异或是相同为0,不同才1。题目说只有一个数字出现一次,其余都是2次,那么我们让其全部异或,剩下来的不就是我们要的数字了吗
这个思路其实在之前的博客中有详细的解析异或思想的算法题
这里就直接放代码了。
// 异或思想 【时间复杂度:O(N) 空间复杂度:O(1)】
int singleNumber(vector<int>& nums)
{
int value = 0;
for (auto e : nums)
{
// 让value去和nums中的所有值异或,相同的数的二进制会被抵消为0,最终剩下的二进制数就是只出现一次的数字。
value ^= e;
}
return value;
}
只出现一次的数字 II
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2]
输出:3
示例 2:
输入:nums = [0,1,0,1,0,1,99]
输出:99
思路:
首先我们知道题目要求时间复杂度是O(N),空间复杂度是O(1)。这就导致了计数法不能再使用、
那我们就要考虑位运算。
我们知道int类型是4个字节大小的,也就是一共占32个比特位。而每个数字都有其对应的二进制码。那么我们只需要存储每一个位中1出现的次数就可以了。如下图所示:
只要是那个位的1的出现次数是3n+1,就说明了,只出现了一次的那个数的二进制在这个位上一定是一个1。这样我们只需要找到所有出现了3n+1次的1。将其翻译出来,就是我们想要的数字。
代码如下:
class Solution {
public:
int singleNumber(vector<int>& nums)
{
int count[32] = {0}; // 用于统计32个位中每个位出现了几次1
for(auto e : nums)
{
for(int n = 0; n < 32; n++)
{
// 在这个循环里,把e中的32个位中是否出现1统计到count中
if(e & (1 << n))// e & (1 << n)的结果是1 就说明e的32位二进制的第n位是1
{
count[n]++; // 第n位是1,那就对应的第n位++。
}
}
}
// 经过上面的循环,我们已经将nums中所有数字的二进制中,每个位的1共出现了几次
// 现在我们需要找到出现了3n+1次1的位数、这个位就是只出现了一次的数字的为1的位
int val = 0;
for(int i = 0; i < 32; i++)
{
// 找到出现了3n+1次1的位数
if(count[i] % 3 == 1)
{
val |= (1 << i);
}
}
return val;
}
};
只出现一次的数字 III
给你一个整数数组 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]
思路:
这个题目其实之前有讲过类似的,这里贴个链接,详细解析去看这个博客异或思想的算法题_异或 算法题-CSDN博客
这里讲下大概思路:
- 我们首先让一个ret去异或上数组中的所有数字,此时ret就是两个只出现了一次的数字的异或结果
- 然后我们要分离出两个只出现一次的数字
- 我们就去找ret中为1的位数,只需要找到一个位就行
- 因为ret中为1的位,就说明两个只出现一次的数字x1和x2的这个位,一个是0,一个是1.
- 我们再让x1去异或上数组中所有第m位为1的数字,这样出现两次的数字就会被抵消掉,x1最终就是第m位为1的只出现一次的数字
- x2同理,那x2就是第m位为0的只出现一次的数字
代码如下:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
int ret = 0;
for(auto e : nums)
{
ret ^= e;
}
// ret此时就是两个只出现一次的数字异或的结果
// 我们要找到ret中出现了1的位数,这两个只出现一次的数字在该位数上一个为0,一个为1
// 我们要将其分离。
// 先找到第m位为1
int m = 0;
while(m < 32)
{
if(ret & (1<<m))
break;
else
m++;
}
//走到这里我们找到了ret中第m位为1
// 我们要根据这个进行分离
// 我们让x1 x2 去接受两个只出现一次的元素
int x1 = 0, x2 = 0;
// 让x1去接受第m位为1的只出现一次的数字
// 让x2去接受第m位为0的只出现一次的数字
for(int i = 0; i < nums.size(); i++)
{
// 让x1去异或数组中第m位是1的数字,出现两次的会抵消,剩下的就是第m位是1的只出现一次的数字
if(nums[i] & (1 << m))
x1 ^= nums[i];
//让x2去异或数组这种第m位是0的数字,出现两次的会抵消,剩下的就是第m位是0的只出现一次的数字
else
x2 ^= nums[i];
}
// 此时的x1和x2就是我们想要的两个只出现一次的数字
vector<int> retarr;
retarr.push_back(x1);
retarr.push_back(x2);
return retarr;
}
};