Leetcode 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
解題思路
這個問題非常簡單,我們可以想像成爬樓梯,每次我們爬上一個新的樓梯我們的下一級臺階就會增加1
。所以我們可以先對輸入陣列A
進行排序,然後爬樓梯,如果樓梯的高度低於我們的目標高度,我們就記錄我們的目標高度和我們實際高度的差加入到result
中即可,否則的話就沒有任何值加到結果中去。例如[3,2,1,2,1,7]
此時我們計算我們的下一個目標是1+1=2
。我們發現1<2
,所以我們要將2-1=1
新增到結果中去。
此時我們的目標應該是2+1=3
。我們發現2<3
所以我們要將3-2=1
新增到結果中去。依次類推
class Solution:
def minIncrementForUnique(self, A):
"""
:type A: List[int]
:rtype: int
"""
A.sort()
res, step = 0, 0
for a in A:
res += max(step, a) - a
step = max(step, a) + 1
return res
其實我在一開始的時候想到的不是上面這個解法,我們首先看到了陣列的大小隻有40000
Hash
表的建立,可以參看這篇文章Hash表的理論基礎與具體實現(詳細教程)。
舉個例子,我們遍歷3,2,1,2,1,7
放到一個大小為40000
的盒子中去,並且記錄放入元素的個數。
此時我們要做的就是將盒子中包含>=1個元素的位置找到,然後將多餘的元素分配出去,分配到哪呢?當然是最近的空位了,這樣就可保障我們挪動的步子最少。
同理我們對於所有元素搜這樣做,並且我們記錄我們挪動的步子總共是多少即可。但是這樣做的時候會存在一個問題
這個時候我們的39998
號位置的元素多了,但是我們沒有空間去存下我們的多餘元素了,這樣怎麼辦?我們首先想到的辦法是增大分配的陣列空間,但是增大多少呢?由於問題的條件0<=A.length<=40000
,所以我們開闢一個80000
的陣列應該就沒問題了。我們將上面的過程整理成程式碼
class Solution:
def minIncrementForUnique(self, A):
"""
:type A: List[int]
:rtype: int
"""
box = [None]*80000
for a in A:
if box[a] != None:
j = a + 1
while j < 80000:
if box[j] == None:
box[j] = j
break
j += 1
else:
result[a] = a
box_sum = 0
for i in box:
if i != None:
box_sum += i
return box_sum - sum(A)
注意,我在實現的時候使用了一點trick
,我們可以不用一開始就將所有元素都放入box
中去。恩,果然超時了,呵呵。問題在哪?我們每次都要對此時遍歷到的元素的後面所有元素做搜尋(查詢空位的位置),這其中是存在著大量的重複計算的。我們怎麼優化呢?我們可以先不將多餘的元素放到空位中去,而是放到它的下一個位置堆積起來
就像推動著黃沙向周圍攤平。藉助這種思想我們就不需要開闢80000
的空間了,我們只需要40000
就可以搞定這個問題,但是這個時候又有一個問題
此時問題是我們多出的3
要怎麼攤開?這就是一個等差數列求和的問題,很簡單
。
class Solution:
def minIncrementForUnique(self, A):
"""
:type A: List[int]
:rtype: int
"""
box, result, max_A = [0]*40000, 0, 0
for a in A:
box[a] += 1
if max_A < a:
max_A = a
for i in range(max_A):
if box[i] <= 1:
continue
ano = box[i] - 1
result += ano
box[i+1] += ano
box[i] = 1
last_ano = box[max_A] - 1
result += (1 + last_ano)*last_ano//2
return result
我將該問題的其他語言版本新增到了我的GitHub Leetcode
如有問題,希望大家指出!!!