題目
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 操作是不能讓數組的每個值唯一的。
提示:
- 0 <= A.length <= 40000
- 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),相比普通排序快一些。最後的運行結果如下: