493. 翻转对

第一次尝试 暴力

采用冒泡排序的方法,依次判断当前数和后面数的关系,记录有无翻转对,时间复杂度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])
尝试用分治法来解决这个问题

  1. 分解 Divide,取mid为数组的中点,划分出左右数组,现在我们将问题划分成两个相同情况但是规模更小的子问题,然后我们递归的求解这两个子问题,显然当前问题的翻转对等于子问题翻转对之和
  2. 解决 Conquer
    考虑如果我们有两个有序数组LR,那么我们设置下标ij,让它们各自指向LR的起点,然后我们进行如下判断
while i<L的长度 或 j<R的长度:
	如果L[i]>2*R[j]:
		由于有序,L剩下的元素和当前元素都能和R[j]组合成翻转对,统计翻转对数量
		移动j,尝试判断更大的数是否能和当前数构成翻转对
	否则:
		移动i,判断更大的数

注意我们只需要求出翻转对的数目,数组中相对顺序并不用保持,所以尽管归并排序会打乱数组顺序,但是这并不会影响结果
经过以上分析,如果我们能够拥有两个有序数组LR,那么求解L+R的数组的翻转对数是简单的,于是我们应该想到我们要对数组进行排序操作,也就是归并排序的目的

  1. 合并 Combine
    为了形成有序的数组,我们需要合并LR数组,这也是归并排序最后的部分
    然后我们再思考一下分解的边界条件,显然是数组的长度为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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值