[LeetCode] 945. Minimum Increment to Make Array Unique 使數組沒有重復數字的最小增量



Given an array of integers A, a move consists of choosing any A[i], and incrementing it by 1.

Return the least number of moves to make every value in A unique.

Example 1:

Input: [1,2,2]
Output: 1
Explanation:  After 1 move, the array could be [1, 2, 3].

Example 2:

Input: [3,2,1,2,1,7]
Output: 6
Explanation:  After 6 moves, the array could be [3, 4, 1, 2, 5, 7].
It can be shown with 5 or less moves that it is impossible for the array to have all unique values.

Note:

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

這道題給了一個數組,說是每次可以將其中一個數字增加1,問最少增加多少次可以使得數組中沒有重復數字。給的兩個例子可以幫助我們很好的理解題意,這里主要參考了 lee215 大神的帖子,假如數組中沒有重復數字的話,則不需要增加,只有當重復數字存在的時候才需要增加。比如例子1中,有兩個2,需要將其中一個2增加到3,才能各不相同。但有時候只增加一次可能並不能解決問題,比如例子2中,假如要處理兩個1,增加其中一個到2並不能解決問題,因此2也是有重復的,甚至增加到3還是有重復,所以一直得增加到4才行,但此時如何知道后面是否還有1,所以需要一個統一的方法來增加,最好是能從小到大處理數據,則先給數組排個序,然后用一個變量 need 表示此時需要增加到的數字,初始化為0,由於是從小到大處理,這個 need 會一直變大,而且任何小於 need 的數要么是數組中的數,要么是某個數字增后的數,反正都是出現過了。然后開始遍歷數組,對於遍歷到的數字 num,假如 need 大於 num,說明此時的 num 是重復數字,必須要提高到 need,則將 need-num 加入結果 res 中,反之若 need 小於 num,說明 num 並未出現過,不用增加。然后此時更新 need 為其和 num 之間的較大值並加1,因為 need 不能有重復,所以要加1,參見代碼如下:


解法一:

class Solution {
public:
    int minIncrementForUnique(vector<int>& A) {
        int res = 0, need = 0;
        sort(A.begin(), A.end());
        for (int num : A) {
        	res += max(need - num, 0);
        	need = max(num, need) + 1;
        }
        return res;
    }
};


假如數組中有大量的重復數字的話,那么上面的方法還是需要一個一個的來處理,來看一種能同時處理大量的重復數字的方法。這里使用了一個 TreeMap 來統計每個數字和其出現的次數之間的映射。由於 TreeMap 可以對 key 自動排序,所以就沒有必要對原數組進行排序了,這里還是要用變量 need,整體思路和上面的解法很類似。建立好了 TreeMap 后開始遍歷,此時單個數字的增長還是 max(need - num, 0),這個已經在上面解釋過了,由於可能由多個,所以還是要乘以個數 a.second,到這里還沒有結束,因為 a.second 這多么多個數字都被增加到了同一個數字,而這些數字應該彼此再分開,好在現在沒有比它們更大的數字,那么問題就變成了將k個相同的數字變為不同,最少的增加次數,答案是 k*(k-1)/2,這里就不詳細推導了,其實就是個等差數列求和,這樣就可以知道將 a.second 個數字變為不同總共需要增加的次數,下面更新 need,在 max(need, num) 的基礎上,還要增加個數 a.second,從而到達一個最小的新數字,參見代碼如下:


解法二:

class Solution {
public:
    int minIncrementForUnique(vector<int>& A) {
        int res = 0, need = 0;
        map<int, int> numCnt;
        for (int num : A) ++numCnt[num];
        for (auto &a : numCnt) {
        	res += a.second * max(need - a.first, 0) + a.second * (a.second - 1) / 2;
        	need = max(need, a.first) + a.second;
        }
        return res;
    }
};

再來看一種聯合查找 Union Find 的方法,這是一種並查集的方法,在島嶼群組類的問題上很常見,可以搜搜博主之前關於島嶼類題目的講解,很多都使用了這種方法。但是這道題乍一看好像跟群組並沒有明顯的關系,但其實是有些很微妙的聯系的。這里的 root 使用一個 HashMap,而不是用數組,因為數字不一定是連續的,而且可能跨度很大,使用 HashMap 會更加省空間一些。遍歷原數組,對於每個遍歷到的數字 num,調用 find 函數,這里實際上就是查找上面的方法中的 need,即最小的那個不重復的新數字,而 find 函數中會不停的更新 root[x],而只要x存在,則不停的自增1,直到不存在時候,則返回其本身,那么實際上從 num 到 need 中所有的數字的 root 值都標記成了 need,就跟它們是屬於一個群組一樣,這樣做的好處在以后的查詢過程中可以更快的找到 need 值,這也是為啥這種方法不用給數組排序的原因,若還是不理解的童鞋可以將例子2代入算法一步一步執行,看每一步的 root 數組的值是多少,應該不難理解,參見代碼如下:


解法三:

class Solution {
public:
    int minIncrementForUnique(vector<int>& A) {
        int res = 0;
        unordered_map<int, int> root;
        for (int num : A) {
        	res += find(root, num) - num;
        }
        return res;
    }
    int find(unordered_map<int, int>& root, int x) {
    	return root[x] = root.count(x) ? find(root, root[x] + 1) : x;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/945


參考資料:

https://leetcode.com/problems/minimum-increment-to-make-array-unique/

https://leetcode.com/problems/minimum-increment-to-make-array-unique/discuss/197687/JavaC%2B%2BPython-Straight-Forward


LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM