注:看这篇文章之前你需要了解& | ^ << >> 这五个运算符,此外,代码均经过测试运行通过
目录
1、只出现一次的数字I(难度:简单)
2、只出现一次的数字II(难度:中等)
3、只出现一次的数字III(难度:中等)
4、 总结:
1、只出现一次的数字I(难度:简单)
题述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度( 即O(N) ),你可以不使用额外空间来实现吗?
示例1:
输入:【2,2,1】
输出:1
题中已给:
class Solution {
public:
int singleNumber(vector<int>& nums) {
}
};
思路:
^(异或操作符求解),^操作符我之前文章写过,请记下以下性质(假设A是一个数):
A^A=0 0^A=A
那就把数组中的数全都^一遍就能得到那个单独的数了
代码如下:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int val = 0;
for (auto e : nums)
{
val ^= e;//与vector容器中的每个值^下
}
return val;
}
};
2、只出现一次的数字II(难度:中等)
题述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度( 即O(N) ),你可以不使用额外空间来实现吗?
示例1:
输入:【2,2,3,2】
输出:3
题中已给:
class Solution {
public:
int singleNumber(vector<int>& nums) {
}
};
思路:
大体思路:找出只出现一次的数对应的二进制位,找到后还原它即可
具体思路:
①、给定四个数,观察对应二进制位,得出出现一次的数对应的相加后的二进制位个数一定是3n+1
故我们先求出所有数32个位中每个位为1的个数
②、32位中每个位,只要位为1的个数是3n+1,就说明他是那个出现一次的数的二进制位,那只要找出这些位,还原这个数即可,怎么还原?
运用 | (或运算) 或者 ^(异或运算)
代码如下:
class Solution {
public:
int singleNumber(vector<int>& nums) {
//1、统计出所有数32个位中1出现的次数
int bits[32] = { 0 };//储存32位的情况
for (auto val : nums)
{
for (size_t i = 0; i < 32; ++i)
{
if ((val >> i) & 1)//或写为if(val & (1 << i))
{//val的32位从低位到高位的每一位都判断是否存在1
//为真说明存在1,则bits数组对应++
bits[i]++;
}
}
}
int num = 0;//要求的那个出现一次的数
for (size_t i = 0; i < 32; ++i)
{
//次数为3N+1的位就是只出现一次的数为1的位
if (bits[i] % 3 == 1)
{
//使num对应的二进制位变为只出现一次的数对应的二进制位
num |= (1 << i);//或写为num ^= (1 << i);
}
}
return num;
}
};
时间复杂度分析:
遍历vector中的每个数一次,故时间复杂度为O(N)
注意:其中遍历的32位是常数次,应该不算到时间复杂度中
3、只出现一次的数字III(难度:中等)
题述:
给定一个整数数组nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。找出只出现了一次的那两个元素。
示例1:
输入:【1,2,1,3,2,5】
输出:【3,5】
说明:
①、结果输出的顺序并不重要,对于上面的例子,【5,3】也是正确答案
②、你的算法应该具有线性时间复杂度( 即O(N) ),你能否仅用常数空间复杂度来实现?
题中已给:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
}
};
思路:
这道题在我写^的文章中写过c语言版本的,思路都是一样的!
https://blog.csdn.net/m0_74044018/article/details/130195037
再说下思路:
1、所有数^后,结果就是只出现一次的两个数^后的结果,重点在于如何分离两个数
2、两个数既然不同,那么一定有一个二进制位(上下相对应的二进制位)不同,因为是^,不同的一位^结果一定为1,那我们只要找到一位为1的即可,用这个1来分离两个数到两组,我们假设这个二进制位是k
3、分为两组:k位为1的分为1组,k位为0的分为另一组,那么两个只出现一次的数一定被分到两组,而每一次都再^一遍,其他的出现两次的数一定会被^没,只会剩下那个只出现一次的数了
代码如下:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
//1、得出只出现一次的两个数^后的结果,val就是结果
int val = 0;
for (auto e : nums)
{
val ^= e;
}
//2、随机找一个两个数不同的二进制位
//二进制位不同则^后结果为1,故我们找个二进制位为1的即可
size_t i = 0;
for (; i < 32; ++i)
{
//找到一个二进制位为1的则break
if (val & (1 << i))//找到的i就是对应第几位
break;
}
//3、分离所有数为两组(那两个只出现一次的数肯定不在一组)
int num1 = 0, num2 = 0;//储存只出现一次的两个数
for (auto e : nums)
{
if (e & (1 << i))
num1 ^= e;
else
num2 ^= e;
}
//4、按题目要求插入这两个数
vector<int>v;
v.push_back(num1);
v.push_back(num2);
return v;
}
};
4、 总结:
凡是涉及到位运算的操作,就按顺序把下面的运算符拿出来套一下:
1、与、或(& |)
2、异或(^)
3、取反(~)