leetcode——Find the Duplicate Number

題目:

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.

Note:

  1. You must not modify the array (assume the array is read only).
  2. You must use only constant, O(1) extra space.
  3. Your runtime complexity should be less than O(n2).
  4. There is only one duplicate number in the array, but it could be repeated more than once.

方法一:二分法

分析:

        n+1個數屬於區間[1, n],必然存在重複(抽屜原則),抽象地,超過(b-a+1)個數屬於區間[a, b]必然也存在重複。比較粗魯的方式是在nums中分別搜索1~n,判斷有沒有重複,時間複雜度o(n^2)不行。一定需要遍歷數組n次麼?可不可以少一點呢?

        考察n/2,遍歷nums,如果小於等於n/2的數超過n/2,那麼區間[1, n/2]中必然存在重複;如果不超過n/2,說明屬於區間[n/2+1, n]中的數(n+1-n/2)大於n/2,因此區間[n/2+1, n]中必然存在重複的數。利用這種方式可以折半查找區間,時間複雜度(nlgn)。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int first = 1, last = nums.size() - 1;//兩邊都是閉區間
        while (first < last) //如果first==last,那麼first就是要找的數
        {
            int mid = first + (last - first) / 2;
            //統計小於等於mid的數的個數
            int count = 0;
            for (auto p : nums) 
            {
                if (p <= mid) 
                {
                    ++count;
                }
            }
            //分成兩個區間[first, n/2]和[n/2+1, last]
            if (count > mid) //[1, 2, ..., n/2]共n/2個數,如果count > n/2,那麼必然存在重複
            {
                last = mid;
            } 
            else 
            {
                first = mid + 1;
            }
        }
        return first;
    }
};

方法二:鏈表找環 https://segmentfault.com/a/1190000003817671

        假設數組中沒有重複,那我們可以做到這麼一點,就是將數組的下標和1到n每一個數一對一的映射起來。比如數組是213,則映射關係爲0->2, 1->1, 2->3。假設這個一對一映射關係是一個函數f(n),其中n是下標,f(n)是映射到的數。如果我們從下標爲0出發,根據這個函數計算出一個值,以這個值爲新的下標,再用這個函數計算,以此類推,直到下標超界。實際上可以產生一個類似鏈表一樣的序列。比如在這個例子中有兩個下標的序列,0->2->3。

        但如果有重複的話,這中間就會產生多對一的映射,比如數組2131,則映射關係爲0->2, {1,3}->1, 2->3。這樣,我們推演的序列就一定會有環路了,這裏下標的序列是0->2->3->1->1->1->1->...,而環的起點就是重複的數。

        所以該題實際上就是找環路起點的題,和Linked List Cycle II一樣。我們先用快慢兩個下標都從0開始,快下標每輪映射兩次,慢下標每輪映射一次,直到兩個下標再次相同。這時候保持慢下標位置不變,再用一個新的下標從0開始,這兩個下標都繼續每輪映射一次,當這兩個下標相遇時,就是環的起點,也就是重複的數。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int slow = 0, fast = 0;
        do 
        {
            slow = nums[slow];
            fast = nums[nums[fast]];//每次跨過兩個結點
        }
        while (slow != fast);
        
        //再定義一個慢指針從起點出發,與slow相遇在環的入口
        int find = 0;
        while (slow != find) 
        {
            slow = nums[slow];
            find = nums[find];
        }
        return find;
    }
};


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章