给定整数数组 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
如有问题,希望大家指出!!!