第一次尝试 暴力
采用冒泡排序的方法,依次判断当前数和后面数的关系,记录有无翻转对,时间复杂度O(n^2),然后TLE了
第二次尝试 先sort,保存每个元素的相对下标
对于每个元素,我们给它绑定它的下标,然后按照元素值排序
排序之后,我们对于当前元素j:(v,ind),直接在数组后面找2*v,如果有大于2v的元素并且下标超过ind,那么ans++,否则
1 没有大于2*v的元素,可以直接结束循环——此时时间复杂度是O(n^2),但是低阶项会小很多
2 有大于2*v的元素,但是下标都大于ind,这是最坏的情况,数组基本有序,此时与原始的冒泡排序时间复杂度基本相当
结果还是超时了,说明O(n^2)的算法是不行的
归并
暂时没想到好的方法能把时间复杂度降到NlogN的,看了一下标签和题解,都提到了归并排序
给定一个数组nums,我们要统计nums中的重要翻转对(i<j且nums[i]>2*nums[j])
尝试用分治法来解决这个问题
- 分解
Divide
,取mid为数组的中点,划分出左右数组,现在我们将问题划分成两个相同情况但是规模更小的子问题,然后我们递归的求解这两个子问题,显然当前问题的翻转对等于子问题翻转对之和 - 解决
Conquer
考虑如果我们有两个有序数组L
和R
,那么我们设置下标i
和j
,让它们各自指向L
和R
的起点,然后我们进行如下判断
while i<L的长度 或 j<R的长度:
如果L[i]>2*R[j]:
由于有序,L剩下的元素和当前元素都能和R[j]组合成翻转对,统计翻转对数量
移动j,尝试判断更大的数是否能和当前数构成翻转对
否则:
移动i,判断更大的数
注意我们只需要求出翻转对的数目,数组中相对顺序并不用保持,所以尽管归并排序会打乱数组顺序,但是这并不会影响结果
经过以上分析,如果我们能够拥有两个有序数组L
和R
,那么求解L+R
的数组的翻转对数是简单的,于是我们应该想到我们要对数组进行排序操作,也就是归并排序的目的
- 合并
Combine
为了形成有序的数组,我们需要合并L
和R
数组,这也是归并排序最后的部分
然后我们再思考一下分解的边界条件,显然是数组的长度为0的时候,不会构成任何翻转对
于是我们可以在归并排序的基础上写出代码,我们用self.ans来持有最终的答案
class Solution:
def reversePairs(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
self.ans = 0
self.mergeSort(nums,0,len(nums)-1)
return self.ans
def mergeSort(self,nums,left,right):
if left == right:
return
mid = (left+right) // 2
self.mergeSort(nums,left,mid)
self.mergeSort(nums,mid+1,right)
i,j=left,mid+1
# 统计元素
while i <= mid and j <= right:
# 如果当前元素构成一个翻转对,那么[i,mid]的元素都能构成一对翻转对
# 我们统计当前的翻转对,然后让j往后移动,此时nums[j]变大,判断i和j是否能继续构成翻转对
if nums[i] > 2*nums[j]:
self.ans += mid - i + 1
j += 1
else:
# 否则,我们往后移动i,nums[i]变大,尝试构成翻转对
i += 1
i,j,k=left,mid+1,0
tmp = [0] * (right - left + 1)
while i <= mid and j <= right:
if nums[i]<=nums[j]:
tmp[k] = nums[i]
k+=1
i+=1
else:
tmp[k] = nums[j]
k+=1
j+=1
while i<=mid:
tmp[k] = nums[i]
k+=1
i+=1
while j<=right:
tmp[k] = nums[j]
k+=1
j+=1
# 注意nums下标是从left开始,tmp下标是从0开始
k = 0
for i in range(left,right+1):
nums[i] = tmp[k]
k += 1