1. 程式人生 > 其它 >287 尋找重複數

287 尋找重複數

技術標籤:# 二分專題javaleetcode演算法資料結構

287. 尋找重複數

給定一個包含 n + 1 個整數的陣列 nums ,其數字都在 1n 之間(包括 1n),可知至少存在一個重複的整數。

假設 nums 只有 一個重複的整數 ,找出 這個重複的數

示例 1:

輸入:nums = [1,3,4,2,2]
輸出:2

示例 2:

輸入:nums = [3,1,3,4,2]
輸出:3

示例 3:

輸入:nums = [1,1]
輸出:1

示例 4:

輸入:nums = [1,1,2]
輸出:1

提示:

  • 2 <= n <= 3 * 104
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums只有一個整數 出現 兩次或多次 ,其餘整數均只出現 一次

進階:

  • 如何證明 nums 中至少存在一個重複的數字?
  • 你可以在不修改陣列 nums 的情況下解決這個問題嗎?
  • 你可以只用常量級 O ( 1 ) O(1) O(1) 的額外空間解決這個問題嗎?
  • 你可以設計一個時間複雜度小於 O ( n 2 ) O(n^2) O(n2) 的解決方案嗎?

思路:

  1. 第一反應一般是對陣列排序,然後遍歷一次,遇到相鄰的兩個元素相等了,那這個數就是答案。但時間複雜度是 O ( n l o g n ) O(nlogn) O(nlogn)(排序),會修改元素組,不滿足進階要求。
  2. 不難想到用一個雜湊表Set存元素,即遍歷一遍陣列,元素都存入雜湊表,當出現重複的時候就是答案。時間複雜度 O ( n ) O(n) O(n),空間複雜度 O ( n ) O(n) O(n),空間複雜度不滿足條件進階要求。
  3. 常見的一種方法就是遍歷一遍陣列,讓陣列中的元素與下標一一對應,如nums[1] = 1,nums[2] = 2,即讓nums[i] = nums[nums[i]],0~n共有n+1個下標,但是數字是1~n,故有一個下標對應的位置需要存兩個數,這個數就是重複的,當然這也改變了原陣列,不符合進階要求。不改變原陣列的話,也可以開闢一個新陣列,用於計數,即遍歷一次陣列,temp[nums[i]]++
    ,之後遍歷一次temp陣列,temp[i] > 1i就是重複的數字,但不符合常量級 O ( 1 ) O(1) O(1) 的額外空間。
  4. 排除上述常見方法後,總算想到殺手鐗,二分演算法了。

注意:分的區間是數的範圍,而不是索引的範圍,如果在這個範圍的數字個數大於區間長度,那麼這個區間內一定有數字重複了。繼續對該區間進行劃分,直到區間長度為1為止。
時間複雜度:分治法為 O ( l o g n ) O(logn) O(logn),每次都要統計區間範圍內的數字,複雜度為 O ( n ) O(n) O(n),所以總的複雜度為 O ( n l o g n ) O(nlogn) O(nlogn)

class Solution {
    public int findDuplicate(int[] nums) {    
        //陣列中的數字在1~n之間,故按數字的範圍二分,left和right起始值如下
        int left = 1;//數字最小為1
        //由題意知,nums.length = n + 1,故n=nums.length-1
        int right = nums.length - 1;
        while(left < right){//當left=right時,即只剩下一個數時停止迴圈,那個數就是結果
            int mid = (left + right)/2;
             //計算整個陣列在前半部分的個數
            int count = getCount(nums,left,mid);
            if(count > (mid - left + 1)){//個數大於區間本該有的個數,該區間出現了重複數字
                right = mid;
            }else{
                left = mid+1;
            }
        }
        return left;//實際上最後返回left還是right都可以,因為他們最後相等
    }

    public int getCount(int[] nums,int left,int right){
        int count = 0;
        for(int num : nums){
            //統計left~right(假設是1~4)這right-left+1(=4)個數字在整個陣列中出現的次數
            if(num >= left && num <= right){
                count++;
            }
        }
        return count;
    }
}

在這裡插入圖片描述