面试题56 - II. 数组中数字出现的次数

题目: 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例:

示例 1:

输入:nums = [3,4,3,3]
输出:4

示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

这个题目和【力扣】136. 只出现一次的数字面试题56 - I. 数组中数字出现的次数是一类题目,基本解法有:哈希表,排序,快慢指针,位运算。位运算是考察的重点,想要时间复杂度和空间复杂度达到最优,就需要进行位运算,那我们下面来看看这道题的三种解法,主要学习位运算的。

一、哈希表,时间O(N),空间S(N)

哈希表,就是[key,value],key保存数字,value保存出现次数,用hash.first可以访问key,hash.second访问second:

  • 所以我们只需要一次遍历,将key,value值填入hash。
  • 判断hash.second判断数字出现次数,等于1,我们输出key即可。
//1.哈希表 O(N)S(N)
int singleNumber1(vector<int>& nums) 
{
	if(nums.size()==0)
		return 0;
	unordered_map<int,int>hash;
	for(int i=0;i<nums.size();i++)
	{
		hash[nums[i]]++;
	}
	for(auto i:hash)
	{
		if(i.second==1)//找到只出现了一次的
			return i.first;
	}
	return 0;
}

二、排序,O(NlogN),S(1)

这个思路也很简单:

  • 将数组排序,将nums[i]和nums[i+2]比较,不一样,则返回nums[i]。
  • i<nums.size()-1,i+=3进行循环即可。
//2.排序
int singleNumber2(vector<int>& nums) 
{
	if(nums.size()==0)
		return 0;
	sort(nums.begin(), nums.end());
	int i=0;
	for(;i<nums.size()-2;i+=3)
	{
		if(nums[i]!=nums[i+2])
		{
			return nums[i];
		}
	}
	return nums[i];//单个元素在结尾
}

三、位运算,O(N),S(1)

有一说一,这个位运算真的很厉害,只要是代码题没有计算机底层原理表示不出来的。它的思路就是,有一个数字出现了3次,那么它的32位表示的二进制之和,1的次数绝对也出现了3次。那么根据这个原理,思路就出来了:

  • 数字用32位二进制存储,我们用b[32]数组,来保存所有数字加起来,32位上1出现的次数,即b[0]表示:所有数字二进制相加,0号位1出现的次数;简单来说就是如果有数字1,2,那么统计1【01】,2【10】相加后,32位数组上1出现的位置,相加为11,即b[0]=1,b[1]=1;
  • 统计1出现的次数,flag=1,用nums[i]&flag则可以判断第一位,将flag<<=1,flag=01,判断第二位,这样循环32次,可以判断nums[i]数字的32位数上1出现的次数。
  • 那么我们需要遍历数组nums,遍历b[32],那么时间复杂度为O(N*32)=O(N).
  • 统计完成后,b[32]存储了数组全部元素和,1出现的次数,那么我们现在将它%3,如果某个元素出现次数为3次,那么1的次数%3后为0,剩下的1就是出现一次。
  • 我们将b[32]中剩下的1,从高位循环累加转换为十进制即可,,我们定义res=0,但如何利用res和b[32]的值得出答案,我们在计算机组成原理中学过,<<可以帮我们进行二进制数值的变化,如01(1),左移10(2),所以现在我们只需要讨论是先<<还是先+,我们可以举例判断:如果有b[i]=0134,对3取余后为0101,那么有:【x】表示当前数值,32位太长,我们以8位举例,前面都补充0即可。
先加再移位先移位再加
加 0:【0000,0000】;移位:【0000,0000】移位:【0000,0000】;加0:【0000,0000】
加1:【0000,0001】;移位:【0000,0010】移位:【0000,0000】;加1:【0000,0001】
加0:【0000,0010】;移位:【0000,0100】移位:【0000,0010】;加0:【0000,0010】
加1:【0000,0101】;移位:【0000,1010】移位:【0000,0100】;加1:【0000,0101】

对比两种办法的结果,我们发现只有先移位,再加,才可以得到最后的结果,res才可以保存b[i]中二进制代表的数值。

那我们举个例子,nums = [3,4,3,3] ,答案为4;深刻理解一下这两步:给b[32]赋值;将b[32]%3后的值转为整数。由于这个示例的最大值为4,所以我们只画出b[32]的前8位即可。

在这里插入图片描述
那代码如下:

int singleNumber(vector<int>& nums) 
{
	if(nums.size()==0)
		return 0;
	int b[32]={0};//存储所有数字每位2进制出现1的次数
	//遍历所有可能
	for(int i=0;i<nums.size();i++)
	{
		int flag=1;
		//unsigned int flag=1;力扣只支持无符号
		for(int j=0;j<32;j++)//32位
		{
			if(nums[i]&flag)//判断每一位是否为1
			{
				b[j]+=1;//保存出现1的次数
			}
			flag<<=1;//判断下一位
		}
	}
	//将b[32]中的二进制,转换为十进制
	int res=0;//保存最后的值
	for(int i=31;i>=0;i--)//从高到低位如0134,从0开始,0101
	{
		res<<=1;//先向左移动,
		//先加再移动 0,00,01,010,011,0110
		//先移动再加 00,01,010,0100,0101,所以选第二种
		res+=(b[i]%3);//数值出现3次,那么它的对应位二进制也是3次,所以%3,剩下的就是只出现一个的
	}
	return res;
}
int main()
{
	vector<int> nums;
	nums.push_back(3);
	nums.push_back(4);
	nums.push_back(3);
	nums.push_back(3);
	cout<<singleNumber(nums);
}

加油哦!六一快乐呀,🍭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值