演算法精講:分享一道值得分享的演算法題

帥地發表於2019-05-04

分享一道leetcode上的題,當然,居然不是放在刷題貼裡來講,意味著分享的這道題不僅僅是教你怎麼來解決,更重要的是這道題引發出來的一些解題技巧或許可以用在其他地方,下面我們來看看這道題的描述。

問題描述

給定一個未排序的整數陣列,找出其中沒有出現的最小的正整數。

示例 1:

輸入: [1,2,0]
輸出: 3
示例 2:

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

輸入: [7,8,9,11,12]
輸出: 1
複製程式碼

說明: 你的演算法的時間複雜度應為O(n),並且只能使用常數級別的空間。

解答

這道題在 leetcode 的定位是困難級別,或許你可以嘗試自己做一下,然後再來看我的解答,下面面我一步步來分析,秒殺的大佬請忽略.....

對於一道題,如果不能第一時間想到最優方案時,我覺得可以先不用那麼嚴格,可以先採用暴力的方法求解出來,然後再來一步步優化。像這道題,我想,如果可以你要先用快速排序先把他們排序,然後在再來求解的話,那是相當容易的,不過 O(nlogn) 的時間複雜度太高,其實我們可以先犧牲下我們的空間複雜度,讓保證我們的時間複雜度為 O(n),之後再來慢慢優化我們的空間複雜度。

方法一:採用集合

我們知道,如果陣列的長度為 n,那麼我們要找的目標數一定是出於 1~n+1 之間的,我們可以先把我們陣列裡的所有數對映到集合裡,然後我們從 1~n 開始遍歷判斷,看看哪個數是沒有在集合的,如果不存在的話,那麼這個數便是我們要找的數了。如果 1~n 都存在,那我們要找的數就是 n+1 了。

不過這裡需要注意的是,在把陣列裡面的數存進集合的時候,對於 小於 1 或者大於 n 的數,我們是不需要存進集合裡的,因為他們不會對結果造成影響,這也算是一種優化吧。光說還不行,還得會寫程式碼,程式碼如下:

    public int firstMissingPositive(int[] nums) {
        Set<Integer> set = new HashSet<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (nums[i] >= 1 && nums[i] <= n) {
                set.add(nums[i]);
            }
        }
        for (int i = 1; i <= n; i++) {
            if (!set.contains(i)) {
                return  i;
            }
        }
        return n + 1;
    }
複製程式碼

採用 bitmap

方法一的空間複雜度在最塊的情況下是 O(n),不知道大家還記不記得位演算法,其實我們是可以利用位演算法來繼續優化我們的空間的,如果不知道位演算法的可以看我直接寫的一篇文章:

1、什麼是bitmap演算法

2、自己用程式碼實現bitmap演算法;

通過採用位演算法,我們我們把空間複雜度減少8倍,即從 O(n) -> O(n/32),但其實 O(n/32) 任然還算 O(n),不過,在實際執行的時候,它是確實能夠讓我們執行的更快的,在 Java 中,已經有自帶的支援位演算法的類了,即 bitSet,如果你沒學過這個類,我相信你也是能一眼看懂的,程式碼如下:

    public int firstMissingPositive2(int[] nums) {
        BitSet bitSet = new BitSet();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (nums[i] >= 1 && nums[i] <= n) {
                bitSet.set(nums[i]);
            }
        }
        for (int i = 1; i <= n; i++) {
            if (!bitSet.get(i)) {
                return  i;
            }
        }
        return n + 1;
    }
複製程式碼

方法3:最終版本

如果這個陣列是有序的,那就好辦了,但是如果我們要把它進行排序的話,又得需要 O(nlogn) 的時間複雜度,那我們有沒有啥辦法把它進行排序,然後時間複雜度又不需要那麼高呢?

答是可以,剛才我們說過,對於那些小於 1 或者大於 n 的數,我們是其實是可以不理的,居然我們,我們需要處理的這些數,他們都是處於 1~n 之間的,那要你給這些處於 1~n 之間的數排序,並且重複的元素我們也是可以忽略掉的,記錄一個就可以了,那麼你能不能在 O(n) 時間複雜度排序好呢?

不知道大家是否還記得我之間寫過的下標法

一些常用的演算法技巧總結

或者是否還記得計數排序?(計數排序其實就是下標法的一個應用了)

不過學過計數排序的朋友可能都知道,計數排序是需要我們開一個新的陣列的,不過我們這裡不需要,這道題我們可以這樣做:例如對於 nums[i],我們可以把它放到陣列下標位 nums[i] - 1 的位置上,這樣子一處理的話,所有 1<=nums[i]<=n 的數,就都是是處於相對有序的位置了。注意,我指的是相對,也就是說對於 1-n 這些數而言,其他 小於 1 或者大於 n 的我們不理的。例如對於這個陣列 nums[] = {4, 1, -1, 3, 7}。

演算法精講:分享一道值得分享的演算法題

讓 nums[i] 放到陣列下標為 nums[i-1]的位置,並且對於那些 nums[i]<=0 或 nums > n的數,我們是可以不用理的,所以過程如下:從下標為 0 開始向右遍歷

1、把 4 放在下標為 3 的位置,為了不讓下標為 3 的數丟失,把下標為 3 的數與 4進行交換。

演算法精講:分享一道值得分享的演算法題

2、此時我們還不能向右移動來處下標為1的數,因為我們當前位置的3還不是處於有序的位置,還得繼續處理,所以把 3 與下標為 2 的數交換

演算法精講:分享一道值得分享的演算法題

3、當前位置額數為 -1,不理它,前進到下標為 1 的位置,把 1 與下標為 0的數交換

演算法精講:分享一道值得分享的演算法題

4、當前位置額數為 -1,不理它,前進到下標為 2 的位置,此時的 3 處於有序的位置,不理它繼續前進,4也是處於有序的位置,繼續前進。

5、此時的 7 > n,不理它,繼續前進。

遍歷完成,此時,那些處於 1~n的數都是處於有序的位置了,對於那些小於1或者大於n的,我們忽略它假裝沒看到就是了

這裡還有個要注意的地方,就是 nums[i] 與下標為 nums[i]-1的數如果相等的話也是不需要交換的。

接下來,我們再次從下標 0 到下標 n-1 遍歷這個陣列,如果遇到 nums[i] != i + 1 的數,,那麼這個時候我們就找到目標數了,即 i + 1。

好吧,我覺得我講的有點囉嗦了,還一步步話題展現過程給你們看,連我自己都感覺有點囉嗦了,大佬勿噴哈。最後程式碼如下:

    public int firstMissingPositive(int[] nums) {
        if(nums == null || nums.length < 1)
            return 1;
        int n = nums.length;
        for(int i = 0; i < n; i++){
            // 這裡還有個要注意的地方,就是 nums[i] 與下標為 nums[i]-1的數如果相等的話
            // 也是不需要交換的。
            while(nums[i] >= 1 && nums[i] <= n && nums[i] != i + 1 && nums[i] != nums[nums[i]-1] ){
                // 和下標為 nums[i] - 1的數進行交換
                int tmp = nums[i];
                nums[i] = nums[tmp - 1];
                nums[tmp - 1] = tmp;
            }
        }
        for(int i = 0; i < n; i++){
            if(nums[i] != i + 1){
                return i + 1;
            }
        }
        return n + 1;
    }
複製程式碼

這道題我覺得還是由挺多值得學習的地方的,例如它通過這道原地交換的方法,使指定範圍內的陣列有序了。

還有就是這種通過陣列下標來解決問題的方法也是一種常用的技巧,例如給你一副牌,讓你打亂順序,之後分發給4個人,也是可以採用這種方法的,詳情可以看這道題:什麼是洗牌演算法

最後推廣下我的公眾號:苦逼的碼農:公眾號裡面已經有100多篇原創檔案,也分享了很多實用工具,海量視訊資源、電子書資源,關注自提。點選掃碼關注哦。 戳我即可關注

相關文章