使數組唯一的最小增量題解

題目

945題 使數組唯一的最小增量(中等)
給定整數數組 A,每次 move 操作將會選擇任意 A[i],並將其遞增 1。
返回使 A 中的每個值都是唯一的最少操作次數。

示例 1:

輸入:[1,2,2]
輸出:1
解釋:經過一次 move 操作,數組將變爲 [1, 2, 3]

示例 2:

輸入:[3,2,1,2,1,7]
輸出:6
解釋:經過 6 次 move 操作,數組將變爲 [3, 4, 1, 2, 5, 7]。
可以看出 5 次或 5 次以下的 move 操作是不能讓數組的每個值唯一的。

提示:

  1. 0 <= A.length <= 40000
  2. 0 <= A[i] < 40000

題解

暴力法,HashSet

從題目中看,挨個+1試試。從不認真看題的我,果然沒看見提示,也沒認爲提示多重要。要查詢某個數字是否已存在,走來想起的就是HashSet。代碼如下:

    public int minIncrementForUnique(int[] A) {
        if(A.length == 0){
            return 0;
        }
        HashSet<Integer> set = new HashSet<>();
        int count = 0;
        set.add(A[0]);
        for (int i=1;i<A.length;i++){
            while (set.contains(A[i])){
                count++;
                A[i]++;
            }
            set.add(A[i]);
        }
        return count;
    }

洋洋得意的我,瞬間提交代碼。坐等通過。最後,我擦,居然超時了。
痛定思痛,怎麼會錯誤呢,估計是hashset存儲太慢導致的,怎麼辦呢?突然就瞄到提示了,原來數字有大小限制啊。那我直接用數組不就完了嗎。於是乎。

暴力法,數組

第一次用的是pp[40000],最後不對,突然想到最壞的情況下是40000個39999,那得定pp[80000],於是乎把上面的代碼修改成數組,代碼如下:

    public int minIncrementForUnique(int[] A) {
        if(A.length == 0){
            return 0;
        }
        int []pp = new int[80000];
        int count = 0;
        pp[A[0]] ++;
        for (int i=1;i<A.length;i++){
            while (pp[A[i]] != 0){
                count++;
                A[i]++;
            }
            pp[A[i]] ++;
        }
        return count;
    }

這一次卻有點忐忑,爲啥,除了去掉了HashSet的存儲慢以外,也沒有別的啥提升啊,又超時了,怎麼辦。還是直接看結果把。
數組暴力法運行結果
雖然沒有超時,這擊敗5%的用戶也是夠了。
既然暴力法效果不行,這種數組題目,我們先排序,排序之後,再遍歷來看看怎麼計算。

排序遍歷法

首先將數組進行排序,排序之後再遍歷數組,看每個數需要增加的次數。

  • 如果當前元素大於上一個元素,保持不變
  • 如果當前元素小於等於上個元素,則增加當前元素,直到大於上一個元素

代碼如下:

    public int minIncrementForUnique(int[] A) {
        Arrays.sort(A); // 先排序
        int curmax = -1; // 當前數組最大值
        int res = 0;
        for (int i = 0; i < A.length; i++) {
            if (A[i] <= curmax) {
                int ai = curmax + 1; // 當前元素 A[i] 需要增加到 curmax + 1
                res += (ai - A[i]); // 記錄自增次數
                A[i] = ai; // 增加當前元素
            }
            curmax = Math.max(curmax, A[i]);
        }
        return res;
    }

來,這次比上一次做了一些提升,我們看一下效果如何:
在這裏插入圖片描述
這是排序,大部分時間花費在排序上面了。如果不做排序呢,其實和第一種暴力法有點類似,我們先做計數,然後再挨個進行計算。

計數再遍歷

這一次計數我們學聰明瞭,不再用HashSet了,浪費時間,直接用數組,因爲只是計算每個元素出現的次數,所以數組大小可以用40000了。
遍歷的邏輯如下:

  • 當前數字出現次數小於等於1,不需要增大
  • 當前數字出現次數大於1,需要增大的數字有出現次數減1個,再把這些數計算次數放到下一個數裏面進行計算,直到最後一個數

代碼如下:

    public int minIncrementForUnique(int[] A) {
        int[] count = new int[40000];
        int max = 0;
        for (int a : A) {
            count[a]++; // 計數
            max = Math.max(max, a); // 計算數組中的最大值
        }
        
        int res = 0;
        for (int j = 0; j < max; j++) {
            if (count[j] > 1) {
                // 有 count[j] - 1 個數需要增加
                res += count[j] - 1; 
                count[j+1] += count[j] - 1;
            }
        }
        
        // count[max] 單獨計算,是因爲可能超出 40000 的邊界
        if (count[max] > 1) {
            int d = count[max] - 1; 
            // 有 d 個數需要增加
            // 分別增加爲 max + 1, max + 2, ... max + d
            // 使用等差數列公式求和
            res += (1 + d) * d / 2;
        }
        return res;
    }

這一次去掉了排序,或者說這一種其實叫做計數排序,時間複雜度爲O(n+k),相比普通排序快一些。最後的運行結果如下:
在這裏插入圖片描述

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