那些神题和它的改编消失的数字IIIIIIIV(全系列,干货满满)
Posted 白龙码~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了那些神题和它的改编消失的数字IIIIIIIV(全系列,干货满满)相关的知识,希望对你有一定的参考价值。
Part I、消失的数字 I
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
O(n)的时间复杂度,O(1)的空间复杂度,这意味着我们只能遍历数组常数次,而且,最好使的哈希现在也不香了。
如何做到呢?
答案是:使用位运算异或
首先回顾一下异或的一些基本特性:
异或的规则是:对应二进制位相同为0,不同为1;
也就是说,两个相同的数异或得到0,并且任何数和0异或得到的还是这个数本身(这是解决本题的根本思想);
同时,异或遵循交换律和分配律,也就是说:a^b^c=a^c^b;
那么根据这个特性,我们可以知道:如果我们把数组中所有的数都异或起来,得到:nums[0]^nums[1]…^nums[size-1]
由于异或满足交换律,那么我们让两两相同的数字异或,必然会得到零(满足上述特性)。我们把那个出现一次的数记为aim,那么经过异或就得到:0^0^……^aim^……^0 = aim
代码实现:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;//0和任何数异或都是那个数本身,所以初始值设为0
for(auto e : nums)
{
ret ^= e;//让ret和所有的数组元素异或
}
return ret;//根据上文推论,异或得到的结果就是只出现一次的数
}
};
Part II、消失的数字 II
本题改编自上题:
数组nums包含从0到n的所有整数,但其中缺了一个。请完成下面的这个函数,以找出那个缺失的整数。你有办法在O(n)时间内完成吗?
本题的数组的特点是:元素时0~n的数字,但缺少了其中一个,让我们找到他。怎么做呢?
方法一:求差
这个方法非常的简单好懂:拿前n项和与数组元素的和作差,得到的就是缺少的那个数。
int missingNumber(int* nums, int numsSize){
int sum = 0, i;
for(i = 0; i < numsSize; i++){
sum += nums[i];
}
return ((numsSize * (1 + numsSize))/2) - sum;
}
但是这种写法带来的问题就是:大概率会溢出(尤其是在当今oj普遍测试样例比较bt的情况下)。
追求效率,何不考虑一下位运算?
方法二:异或法
不知上一题的异或对大家有没有一些启发。我们知道,数组中包含0~n,其中只有一个出现了0次,其余出现了1次,而上一题是:只有一个出现了1次,其余出现了2次。
如何实现本题到上一题的转换呢?
很简单:给数组再添上0~n之间的所有数呗。
然后我们就发现:那个本来出现0次的现在出现了1次,其余数字都出现了2次。于是我们再把所有数组元素异或起来得到的就是我们想要的数字了。
不过,给数组再追加n个元素未免太浪费了。那这0~n个数字去哪里找呢?答案是:循环变量!我们遍历数组,那么循环变量就是0~n-1,剩下的那个n,我们可以当做ret的初始值,于是乎我们就可以得到:
ret=Size^0^1^2…^(Size-1)^nums[0]^nums[1]…^nums[Size-1]
于是,所有在等式中出现两次的数都异或为0了,最后剩下出现一次的,也就是原来缺少的那个数。
代码实现:
class Solution {
public:
int missingNumber(vector<int>& nums) {
int Size= nums.size();
int ret = Size;
for(int i = 0; i < Size; i++)
{
ret ^= nums[i];
ret ^= i;
}
return ret;
}
};
Part III、消失的数字 III
道生一,一生二,二生三,三生万物!让我们来康康第三个改编版本!
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
本题又和之前的有所不同:之前是出现一次的数字只有一个,但这一次有两个!分别记为aim1和aim2。
如果我们把所有数异或起来,最终得到的结果就是:aim1^aim2
那么,如何将这两个出现了一次的数区分开来呢?
既然使用了位运算,我们不妨从二进制位的角度出发:两个数对应二进制位相同则异或后为0,不同则为1。
那么我们只要找到这两个数异或后的结果的二进制中1的位置,那么通过它我们就可以区分这两个数。
举个例子:
假设剩下1和3,它们的二进制分别为:
01
11
异或后得到10,这里的1出现的位置就是1和3对应的二进制位不同的地方。
我们找到突破点就可以写代码了:
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
int res = 0;
for(auto e : nums)
{
res ^= e;//得到所有数异或起来的结果
}
//最终的res就是那两个只出现一次的数异或的结果
//找到res二进制中1的对应比特位
//这意味着它们在这一位上的二进制数是不同的
//根据这个差别进行分类
int i = 0;
for(i = 0;i < 32; i++)
{
if(res >> i & 1 == 1)
{
break;
}
}
//第i位就是区分这两个数的比特位
vector<int> v;
v.push_back(0);
for(auto e : nums)
{
if(e >> i & 1 == 1)
{
v[0] ^= e;//将所有第i位是1的异或起来
}
}//得到的最终结果就是两个只出现一次的元素的其中一个
//再让它跟res异或,得到的就是另外一个
v.push_back(res ^ v[0]);
return v;
}
};
Part IV、消失的数字 IV
想不到吧,还有改编题——
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
注意:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
这次的数组又出新花样了:依旧是有一个只出现了一次,但是剩下的都出现了三次。这又怎么办呢?同样的,我们追求的是效率。这次我们依然从二进制位的角度看问题:
假设一个数组是这样的:{1,1,1,2},那么它们的二进制对应的就是01、01、01、10,我们可以看到:第一位为1的有3个,第二位为1的有1个。
什么意思呢?
我们再看一组例子:{7,7,7,5,3,3,3};
7对应二进制111,5对应101,3对应011。
我们依然统计它们的二进制位中1出现的次数,发现:
1、第一位为1的有7个
2、第二位为1的有6个
3、第三位为1的有4个。
和第一个例子结合起来可以看到:有的二进制位上1的个数是3的倍数,即3n,而其他的都是3的倍数再加1,即3n+1,而这些位刚好是那个只出现一次的数中1出现的位置。
比如:
第一个例子中,合计出现次数为3n+1的位是第二位,而只出现一次的2的二进制就是10。
第二个例子中,合计出现次数为3n+1的位是第一位和第三位,而只出现一次的5的二进制就是101。
综上所述:我们可以统计数组元素的二进制中1出现的位置并计算它们出现的次数,根据这个次数是否是3n+1来判断哪个数是只出现一次的。
代码实现:
class Solution {
public:
int singleNumber(vector<int>& nums)
{
int ret = 0;
for(int i = 0; i < 32; i++)
{
//记bit位分别是第0位~第31位
int cnt = 0;//统计每一个数第i位出现1的次数
for(auto e : nums)
{
if((e >> i & 1) == 1)
{
++cnt;
}
}
if(cnt % 3 == 1)
{
//cnt会出现3n以及3n+1的情况
//3n说明那个出现一次的数第i位不是1
//3n+1说明那个出现一次的数第i位是1
ret += 1 << i;//根据第几位是1可以计算出ret是多少
}
}
return ret;
}
};
Part V、检验自己
如何不使用临时变量实现a,b两个数值的交换?
a=a^b;
b=a^b;
a=a^b;
第一次令a=a^b,第二次b=a^b^b,也就是a,最后a=a^b^a,也就是b;
听懂了嘛?没懂得同学可以再看一看上面的解析,好好品味一下哦,同时也欢迎评论区提问。
//博主定期更新刷题、面试等相关知识供大家提高自己的能力,欢迎关注哦。
以上是关于那些神题和它的改编消失的数字IIIIIIIV(全系列,干货满满)的主要内容,如果未能解决你的问题,请参考以下文章