LeetCode287.尋找重複數

LeetCode287.尋找重複數

題目描述

思路

不加限制:

  • 使用哈希表判重,違反限制2
  • 將原始數組排序,找排序後重復的數相鄰,違反規則1
  • 類似LeetCode41:原地哈希的思路,當兩個數發現要放到同一個地方時就發現此元素,但是違反限制1

不考慮說明(即不考慮額外空間的限制)

​ 要是不考慮額外空間的限制,直接引入一個SetHash來保存數組,遇到沒遇到過的就放進去,遇到遇到過的就直接彈出

考慮說明

二分查找

​ 既然要定位數,這個數恰好是一個整數,可以在[整數的有效範圍內]做二分查找,但是比較煩的一點就是反覆查看數組好幾次。

  • 由於題目中有很多有利條件:查找的數是一個整數,並且給出這個整數的範圍。於是可以使用二分查找定位在一個區間裏的整數
  • 思想是先猜一個數(有效範圍【left,right】裏面的中間數mid),然後統計原始數組中小於等於這個中間數的元素的個數cnt。如果cnt嚴格大於mid。那麼根據抽屜原理就在其中。
  • 此方法是空間敏感的,即以時間換空間。
快慢指針

​ 需要對【Floyd判圈算法】(龜兔賽跑算法)

​ 我們對nums[]數組建圖,每個位置i連一條i→nums[i]的邊,由於存在重複數字target,因此整張圖一定存在環,我們要找到的target就是這個環的入口。

​ 我們先設置慢指針slow和快指針fast,慢指針每次走一步,快指針每次走兩步。根據【Floyd判圈算法】兩個指針在有環的情況下一定會相遇。

​ 此時我們再將slow放置在起點0.兩個指針每次同時移動一步,相遇的點就是答案

解釋:

​ 假設環長爲 L,從起點到環的入口的步數是 a,從環的入口繼續走 b 步到達相遇位置,從相遇位置繼續走 c 步回到環的入口,則有 b+c=L,其中 L、a、b、c 都是正整數。

​ 根據上述定義,慢指針走了 a+b 步,快指針走了 2(a+b)步。從另一個角度考慮,在相遇位置,快指針比慢指針多走了若干圈,因此快指針走的步數還可以表示成 a+b+kL,其中 k 表示快指針在環上走的圈數。聯立等式,可以得到
​ 2(a+b)=a+b+kL

解得 a=kL-b,整理可得

​ a=(k-1)L+(L-b)=(k-1)L+c

​ 從上述等式可知,如果慢指針從起點出發,快指針從相遇位置出發,每次兩個指針都移動一步,則慢指針走了 a 步之後到達環的入口,快指針在環裏走了 k-1 圈之後又走了 c 步,由於從相遇位置繼續走 c 步即可回到環的入口,因此快指針也到達環的入口。兩個指針在環的入口相遇,相遇點就是答案。

代碼

解法一:哈希表判重

    public int findDuplicate(int[] nums) {
        Set<Integer> a=new HashSet<>();
        for(int i:nums){
            if(a.contains(i)){
                return i;
            }else{
                a.add(i);
            }
        }
        return 0;
    }

解法二:二分查找

 public static int findDuplicate(int[] nums) {
        int len = nums.length;
        int left = 1;
        int right = len - 1;
        while (left < right) {
            // 在 Java 裏可以這麼用,當 left + right 溢出的時候,無符號右移保證結果依然正確
            int mid = (left + right) >>> 1;

            int cnt = 0;
            for (int num : nums) {
                if (num <= mid) {
                    cnt += 1;
                }
            }

            // 根據抽屜原理,小於等於 4 的個數如果嚴格大於 4 個
            // 此時重複元素一定出現在 [1, 4] 區間裏
            if (cnt > mid) {
                // 重複元素位於區間 [left, mid]
                right = mid;
            } else {
                // if 分析正確了以後,else 搜索的區間就是 if 的反面
                // [mid + 1, right]
                left = mid + 1;
            }
        }
        return left;
    }

解法三:快慢指針

public int findDuplicate(int[] nums) {
        int slow = 0, fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while (slow != fast);
        slow = 0;
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章