那些神题和它的改编消失的数字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(全系列,干货满满)的主要内容,如果未能解决你的问题,请参考以下文章

技术分享 | 115个Java面试题和答案—终极列表

[日常训练]yayamao的神题

java 面试

LeetCode 448.找到所有数组中消失的数字 - JavaScript

抹不掉的消费数字足迹!那些悄然消失的花呗额度……

抹不掉的消费数字足迹!那些悄然消失的花呗额度……