解法
记录一下,是个好题,解法超多
首先需要明确的是,最后相同的那个数值一定是数组中的元素,因为这样能省下一些跳数
解法一:暴力
略
解法二:排序+求和
当我们以nums[i]
作为最后的结果的时候,那么前面的数都不超过nums[i]
,所以前面的数的总步骤为nums[i]*i-sum(nums[:i])
,
后面的数都不小于nums[i]
,后面的数的总步骤为sum(nums[i:])-nums[i]*(n-i)
和可以事先求好,最后选出最小的就行
class Solution(object):
def minMoves2(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums.sort()
n = len(nums)
p = [0]
for a in nums:
p.append(p[-1]+a)
ans = 0x7fffffff
for i,a in enumerate(nums):
ans = min(ans, a*i-p[i]+p[-1]-p[i+1]-a*(n-i-1))
return ans
解法三:排序+中位数
接下来要到这个题的一个重要性质了,最后那个相同数值一定就是数组的中位数
为什么呢?
假设我们在中位数为m
的条件下算出一个解moves
。
不防假设小于目标值的数字个数为k1
,大于目标值的数字个数为k2
- 如果我们要把目标值变小
h
,即最后数组里的数都变成m-h
,那么会有k1
个数的步数减少h
,但是会有k2
个数的步数增大h
,由于m-h
在中位数的左边,所以肯定有k1<k2
,所以最后相比于目标值为中位数得到的moves
会增加 - 同理,如果我们把目标值增大
h
,那么会有k1
个数增加h
,有k2
个数减少h
,而由于又有k1>k2
,所有最后表现还是增加
综上,中位数时候的值是最小的
可以直接排序找中位数
class Solution(object):
def minMoves2(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums.sort()
n = len(nums)
j = n//2
mid = nums[j]
return mid*j-sum(nums[:j])+sum(nums[j:])-mid*(n-j)
解法四:基于快排找中位数
套路算法辽……,由于用来partition的值如果经常不幸选中成了边上的元素,这样算法会退化成
O
(
n
2
)
O(n^2)
O(n2),有可能会超时
直接加了个简单的随机……
class Solution(object):
def minMoves2(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
import random
n = len(nums)
def partition(l,r,k):
rr = random.randint(l,r)
nums[rr],nums[r] = nums[r],nums[rr]
ll = l
for i in xrange(l,r):
if nums[i]<nums[r]:
nums[ll],nums[i] = nums[i],nums[ll]
ll += 1
nums[ll],nums[r] = nums[r],nums[ll]
if ll==k:
return nums[ll]
if ll<k:
return partition(ll+1,r,k)
return partition(l,ll-1,k)
j = n//2
tar = partition(0,n-1,j)
return tar*j-sum(nums[:j])+sum(nums[j:])-tar*(n-j)
解法五:排序但不找中位数
不管最后统一成什么数,我们假设为k
,那么数组的最大值max
和最小值min
,一定一个在k
上一个在k
下,那么它俩的步数加起来就是:
max-k+k-min=max-min
跟具体是目标值是啥没关系了
所以我们可以一对一对地算步数,这样就不需要找目标值了
class Solution(object):
def minMoves2(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums.sort()
ans = 0
l,r = 0,len(nums)-1
while l<r:
ans += nums[r]-nums[l]
l += 1
r -= 1
return ans