题意整理:

基本题意

​ 或许是一道经典的面试题。

​ 给出一个长度为 的整数数列 ,保证里面 都出现至少一次,同时有且仅有一个数字出现过两次。

​ 找出这个出现过两次的数字。

数据范围与性质

​ 要求使用时间复杂度为 ,额外空间(指输入数据的 以外的空间)为 的算法。


解法1:朴素算法(不满足空间限制)

【算法】朴素算法

实现与分析

​ 用一个额外的 数组统计每个数字出现的次数,如果一个数字的出现次数超过 ,则它就是答案。

​ 用 循环和数组实现即可。

​ 时空间复杂度

参考代码

//c++
class Solution {
public:
    int cnt[100001];
    int search(int n, vector<int>& a) {
          memset(cnt,0,sizeof(cnt));//初始化cnt数组为 0
        for(int i=0;i<=n;i++)
            cnt[a[i]]++;//统计每个数字的出现次数
        for(int i=1;i<=n;i++)
            if(cnt[i]>1)
                return i;//找到答案

        return 0;
    }
};

吐槽

​ 牛客并没有限制 时间和 空间,这个代码在牛客上依然可以


解法2:数字分拣

【算法】小技巧

实现与分析

​ 思考如何利用好输入数列本身的空间。

​ 注意,数据范围都在 以内,如果我们能够把输入数列中的 放到 位置,那么肯定有一个多出来的数字它没有地方放,只能放在

​ 那么我们不妨就这么做,目标是把每个数字分拣,让 。于是我们对 进行考虑:

  • 我们就把 交换,换句话说,这可以让数字 到达它目标位置。如果发现 了,说明答案就是 ,这时结束算法。

​ 我们通过一个例子来演示算法过程:

考虑某个样例的执行过程:

4,[1,3,4,2,1]

图片说明

图片说明

图片说明

图片说明

图片说明

答案为

参考代码

//c++
class Solution {
public:

    int search(int n, vector<int>& a) {
        for(int i=0;i<=n;i++)
        {
            if(a[0]==a[a[0]]) return a[0];//如果找到重复的数字了,结束算法

            int t=a[a[0]];
            a[a[0]] = a[0];//否则把当前数字交换到其目标位置
            a[0] = t;
        }
        return 0;
    }
};

​ 上面的算法总是能在 步结束,因为每次交换两个数字的时候,总能把一个数字放到目标位置去,一旦所有数字都到达目标位置了,算法一定会结束,所以时间复杂
​ 因为额外的空间仅仅是交换两个数时用到的一个空间,所以额外空间复杂度


补充解法:运算法

加和运算法:

因为数据保证数字 各出现一次,然后目标数字额外出现一次,那么显然 就是答案,即用所有数字和减去 的结果就得到了额外出现一次的数字。
代码:

class Solution {
public:

    int search(int n, vector<int>& a) {
          long long sum_a=0,sum=0;
        for(int i=0;i<=n;i++)
            sum_a+=a[i];
        for(int i=1;i<=n;i++)
            sum+=i;
        return sum_a-sum;
    }
};

这样只需要维护 的额外空间来进行累加运算,累加过程需要遍历大小为 的数组,因此时间复杂度 ,额外空间复杂度

异或和运算法:

二进制整数的按位异或运算满足传说中的交换律,并且自身就是自己的逆运算。
表示按位异或运算。那么可以的得到:
于是就有
也就是说,只需要求出 的异或和,以及 的异或和,两者相互异或就是答案。
代码:

class Solution {
public:

    int search(int n, vector<int>& a) {
          int sum_a=0,sum=0;
        for(int i=0;i<=n;i++)
            sum_a^=a[i];
        for(int i=1;i<=n;i++)
            sum^=i;
        return sum_a^sum;
    }
};

同样,只需要维护 的额外空间来进行累异或运算,需要遍历大小为 的数组,因此时间复杂度 ,额外空间复杂度