leetcode 287. 寻找重复数

问题描述

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3
说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

代码1

给一个时间复杂度为\(O(N^2)\)的算法:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n = nums.size(),i,j;
        for(i = 0; i < n; i++)
        {
            for(j = i+1;j < n; j++)
            {
                if(nums[i] == nums[j])
                    return nums[i];
            }
        }
        return -1;
    }
};

结果:

执行用时 :2320 ms, 在所有 C++ 提交中击败了5.04%的用户
内存消耗 :10.9 MB, 在所有 C++ 提交中击败了11.92%的用户

代码2

给一个时间复杂度为\(O(N\log(N))\),但是移动数据的算法:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n = nums.size(),i;
        sort(nums.begin(),nums.end());
        for(i = 0; i < n-1; i++)
        {
            if(nums[i] == nums[i+1])
                break;
        }
        return nums[i];
    }
};

结果:

执行用时 :28 ms, 在所有 C++ 提交中击败了10.50%的用户
内存消耗 :11.1 MB, 在所有 C++ 提交中击败了11.92%的用户

代码3

给一个时间复杂度为\(O(N)\)但是空间复杂度也是\(O(N)\)的算法:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n = nums.size(),i;
        vector<int> table(n,0);
        for(i = 0; i < n; i++)
        {
           if(table[nums[i]] == 0)
              table[nums[i]]++;
           else
              break;
        }
        return nums[i];
    }
};

结果:

执行用时 :12 ms, 在所有 C++ 提交中击败了74.16%的用户
内存消耗 :10.9 MB, 在所有 C++ 提交中击败了11.92%的用户

代码4

下面给一个时间复杂度为\(O(N)\)空间复杂度为常数,但是要修改原数据的算法:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n = nums.size(),i,index;
        for(i = 0; i < n; i++)
        {
           index = abs(nums[i]) - 1;
           if(nums[index] < 0)
                break;
           else
                nums[index] *= -1;
        }
        return fabs(nums[i]);
    }
};

结果:

执行用时 :20 ms, 在所有 C++ 提交中击败了23.46%的用户
内存消耗 :10.8 MB, 在所有 C++ 提交中击败了11.92%的用户

该方法和645. 错误的集合有类似之处。

代码5(二分法)

这是一个符合题意的算法,时间复杂度\(O(N\log(N))\),空间复杂度为\(O(1)\):

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int n = nums.size(),i,num,left = 1,right= n,middle;
        while(left < right)
        {
            middle = left+(right-left)/2;
            num = 0;
            //统计[1,middle]有多少个元素,如果元素个数少于等于middle,说明重复的元素在[middle+1,right]中,如果元素个数大于middle,说明重复元素在[left,middle]中
            for(i = 0; i < n; i++)
                if(nums[i] <= middle)
                    ++num;
            if(num <= middle) left = middle+1;
            else right = middle;
        }
        return left;
    }
};

结果:

执行用时 :36 ms, 在所有 C++ 提交中击败了8.16%的用户
内存消耗 :10.8 MB, 在所有 C++ 提交中击败了11.92%的用户

代码6(快慢指针)

这道题和160. 相交链表类似,时间复杂度为\(O(N)\),空间复杂度为\(O(1)\),思路如下:可以使用数组配合下标,抽象成链表问题。但是难点是要定位环的入口位置。举个例子:nums = [2,5, 9 ,6,9,3,8, 9 ,7,1],构造成链表就是:2->[9]->1->5->3->6->8->7->[9],也就是在[9]处循环。但是会在环内的[9]->1->5->3->6->8->7->[9]任何一个节点追上,不一定是在[9]处相碰。快指针每次走2步,慢指针每次走1步。设相遇时快指针走\(t_2\)步,慢指针走\(t_1\)步,环长为\(c\),因此\(t_2 = 2\times t_1\)。 则相遇时, 快指针比慢指针多走一个环的长度,即 \(t_2 = t_1 + c = 2t_1,t_1 = c\),即慢指针走了\(c\)步(恰好是环的长度),假设环之前的长度为\(m\)(也是要求的),则慢指针在环内走了\(c-m\)步,因此再让快指针从头开始出发,快慢指针一定在\(m\)处相遇。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0,fast = 0;
        do{
            slow = nums[slow];
            fast = nums[nums[fast]];
        }while(slow!=fast);
        fast = 0;
        do{
            slow = nums[slow];
            fast = nums[fast];
        }while(slow!=fast);
        return slow;
    }
};

结果:

执行用时 :24 ms, 在所有 C++ 提交中击败了14.95%的用户
内存消耗 :10.6 MB, 在所有 C++ 提交中击败了11.92%的用户
posted @ 2020-03-26 08:30  曲径通霄  阅读(252)  评论(0编辑  收藏  举报